通过createComponent创建组件VNode,然后会走到vm.update,执行vm.patch去把VNode转换成真正的DOM节点

patch的过程会调用createElm创建元素节点

  1. function createElm (
  2. vnode,
  3. insertedVnodeQueue,
  4. parentElm,
  5. refElm,
  6. nested,
  7. ownerArray,
  8. index
  9. ) {
  10. // ...
  11. if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
  12. return
  13. }
  14. // ...
  15. }

createComponent方法

  1. function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  2. let i = vnode.data
  3. // 若vnode是一个组件VNode,则为true,且i就是init钩子函数
  4. if (isDef(i)) {
  5. const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
  6. if (isDef(i = i.hook) && isDef(i = i.init)) {
  7. i(vnode, false /* hydrating */)
  8. }
  9. // after calling the init hook, if the vnode is a child component
  10. // it should've created a child instance and mounted it. the child
  11. // component also has set the placeholder vnode's elm.
  12. // in that case we can just return the element and be done.
  13. if (isDef(vnode.componentInstance)) {
  14. initComponent(vnode, insertedVnodeQueue)
  15. // 完成组件的整个patch过程后执行以下代码完成组件的DOM插入
  16. // 如果组件patch过程中又创建了子组件,那么DOM的插入顺序是先子后父
  17. insert(parentElm, vnode.elm, refElm)
  18. if (isTrue(isReactivated)) {
  19. reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
  20. }
  21. return true
  22. }
  23. }
  24. }

init钩子

  1. init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
  2. if (
  3. vnode.componentInstance &&
  4. !vnode.componentInstance._isDestroyed &&
  5. vnode.data.keepAlive
  6. ) {
  7. // kept-alive components, treat as a patch
  8. const mountedNode: any = vnode // work around flow
  9. componentVNodeHooks.prepatch(mountedNode, mountedNode)
  10. } else {
  11. // 通过createComponentInstanceForVnode创建一个Vue实例
  12. const child = vnode.componentInstance = createComponentInstanceForVnode(
  13. vnode,
  14. activeInstance
  15. )
  16. // 调用$mount方法挂载子组件
  17. child.$mount(hydrating ? vnode.elm : undefined, hydrating)
  18. // 客户端渲染相当于执行
  19. // child.$mount(undefined, false)
  20. // 进而调用mountComponent方法,再进而执行vm._render()方法
  21. }
  22. },

createComponentInstanceForVnode

createComponentInstanceForVnode函数构造的一个内部组件的参数,然后执行new vnode.componentOptions.Ctor(options)

  1. export function createComponentInstanceForVnode (
  2. // we know it's MountedComponentVNode but flow doesn't
  3. vnode: any,
  4. // activeInstance in lifecycle state
  5. parent: any
  6. ): Component {
  7. const options: InternalComponentOptions = {
  8. _isComponent: true, // 表明是一个组件
  9. _parentVnode: vnode,
  10. parent // 表示当前激活的组件实例
  11. }
  12. // check inline-template render functions
  13. const inlineTemplate = vnode.data.inlineTemplate
  14. if (isDef(inlineTemplate)) {
  15. options.render = inlineTemplate.render
  16. options.staticRenderFns = inlineTemplate.staticRenderFns
  17. }
  18. // vnode.componentOptions.Ctor对应的就是子组件的构造函数
  19. return new vnode.componentOptions.Ctor(options)
  20. }

子组件的实例实际上就是在这个时机执行的,并且它会执行实例的_init方法,这个过程有一些和之前不同的地方需要挑出来说
在src/core/instance/init.js 中

  1. Vue.prototype._init = function (options?: Object) {
  2. const vm: Component = this
  3. // a uid
  4. vm._uid = uid++
  5. let startTag, endTag
  6. /* istanbul ignore if */
  7. if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  8. startTag = `vue-perf-start:${vm._uid}`
  9. endTag = `vue-perf-end:${vm._uid}`
  10. mark(startTag)
  11. }
  12. // a flag to avoid this being observed
  13. vm._isVue = true
  14. // merge options
  15. // _isComponent为true
  16. if (options && options._isComponent) {
  17. // optimize internal component instantiation
  18. // since dynamic options merging is pretty slow, and none of the
  19. // internal component options needs special treatment.
  20. // 走到这里
  21. initInternalComponent(vm, options)
  22. } else {
  23. vm.$options = mergeOptions(
  24. resolveConstructorOptions(vm.constructor),
  25. options || {},
  26. vm
  27. )
  28. }
  29. /* istanbul ignore else */
  30. if (process.env.NODE_ENV !== 'production') {
  31. initProxy(vm)
  32. } else {
  33. vm._renderProxy = vm
  34. }
  35. // expose real self
  36. vm._self = vm
  37. initLifecycle(vm)
  38. initEvents(vm)
  39. initRender(vm)
  40. callHook(vm, 'beforeCreate')
  41. initInjections(vm) // resolve injections before data/props
  42. initState(vm)
  43. initProvide(vm) // resolve provide after data/props
  44. callHook(vm, 'created')
  45. /* istanbul ignore if */
  46. if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  47. vm._name = formatComponentName(vm, false)
  48. mark(endTag)
  49. measure(`vue ${vm._name} init`, startTag, endTag)
  50. }
  51. // 组件初始化时是不传el的,因此组件是自己接管了$mount的过程
  52. if (vm.$options.el) {
  53. vm.$mount(vm.$options.el)
  54. }
  55. }

initInternalComponent

  1. export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  2. const opts = vm.$options = Object.create(vm.constructor.options)
  3. // doing this because it's faster than dynamic enumeration.
  4. const parentVnode = options._parentVnode
  5. // 把之前通过createComponentInstanceForVnode函数传入的几个参数合并到内部的选项$options里了
  6. opts.parent = options.parent
  7. opts._parentVnode = parentVnode
  8. const vnodeComponentOptions = parentVnode.componentOptions
  9. opts.propsData = vnodeComponentOptions.propsData
  10. opts._parentListeners = vnodeComponentOptions.listeners
  11. opts._renderChildren = vnodeComponentOptions.children
  12. opts._componentTag = vnodeComponentOptions.tag
  13. if (options.render) {
  14. opts.render = options.render
  15. opts.staticRenderFns = options.staticRenderFns
  16. }
  17. }

vm._render

  1. Vue.prototype._render = function (): VNode {
  2. const vm: Component = this
  3. const { render, _parentVnode } = vm.$options
  4. // _parentVnode就是当前组件的父VNode
  5. if (_parentVnode) {
  6. vm.$scopedSlots = normalizeScopedSlots(
  7. _parentVnode.data.scopedSlots,
  8. vm.$slots,
  9. vm.$scopedSlots
  10. )
  11. }
  12. // set parent vnode. this allows render functions to have access
  13. // to the data on the placeholder node.
  14. // vnode的parent指向了_parentVnode,也就是vm.$vnode
  15. vm.$vnode = _parentVnode
  16. // render self
  17. let vnode
  18. try {
  19. // There's no need to maintain a stack because all render fns are called
  20. // separately from one another. Nested component's render fns are called
  21. // when parent component is patched.
  22. currentRenderingInstance = vm
  23. vnode = render.call(vm._renderProxy, vm.$createElement)
  24. } catch (e) {
  25. handleError(e, vm, `render`)
  26. // return error render result,
  27. // or previous vnode to prevent render error causing blank component
  28. /* istanbul ignore else */
  29. if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
  30. try {
  31. vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
  32. } catch (e) {
  33. handleError(e, vm, `renderError`)
  34. vnode = vm._vnode
  35. }
  36. } else {
  37. vnode = vm._vnode
  38. }
  39. } finally {
  40. currentRenderingInstance = null
  41. }
  42. // if the returned array contains only a single node, allow it
  43. if (Array.isArray(vnode) && vnode.length === 1) {
  44. vnode = vnode[0]
  45. }
  46. // return empty vnode in case the render function errored out
  47. if (!(vnode instanceof VNode)) {
  48. if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
  49. warn(
  50. 'Multiple root nodes returned from render function. Render function ' +
  51. 'should return a single root node.',
  52. vm
  53. )
  54. }
  55. vnode = createEmptyVNode()
  56. }
  57. // set parent
  58. // vnode的parent指向了_parentVnode,也就是vm.$vnode
  59. vnode.parent = _parentVnode
  60. return vnode
  61. }

vm._update

定义在 src/core/instance/lifecycle.js 中

  1. // 保持当前上下文的Vue实例,是在lifecycle模块的全局变量
  2. // 在调用createComponentInstanceForVNode方法的时候从lifecycle模块获取,且作为参数传入
  3. export let activeInstance: any = null
  4. export function setActiveInstance(vm: Component) {
  5. const prevActiveInstance = activeInstance
  6. activeInstance = vm
  7. return () => {
  8. activeInstance = prevActiveInstance
  9. }
  10. }
  11. Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  12. const vm: Component = this
  13. const prevEl = vm.$el
  14. const prevVnode = vm._vnode
  15. const restoreActiveInstance = setActiveInstance(vm)
  16. // vnode是vm_render()返回的组件渲染VNode
  17. // vm.vnode和vm.$vnode的关系是一种父子关系 vm._vnode.parent === vm.$vnode
  18. vm._vnode = vnode
  19. // Vue.prototype.__patch__ is injected in entry points
  20. // based on the rendering backend used.
  21. if (!prevVnode) {
  22. // initial render
  23. vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
  24. } else {
  25. // updates
  26. // 调用vm.__patch__渲染VNode
  27. vm.$el = vm.__patch__(prevVnode, vnode)
  28. }
  29. restoreActiveInstance()
  30. // update __vue__ reference
  31. if (prevEl) {
  32. prevEl.__vue__ = null
  33. }
  34. if (vm.$el) {
  35. vm.$el.__vue__ = vm
  36. }
  37. // if parent is an HOC, update its $el as well
  38. if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
  39. vm.$parent.$el = vm.$el
  40. }
  41. // updated hook is called by the scheduler to ensure that children are
  42. // updated in a parent's updated hook.
  43. }

实际上 JavaScript 是一个单线程,Vue 整个初始化是一个深度遍历的过程,在实例化子组件的过程中,它需要知道当前上下文的 Vue 实例是什么,并把它作为子组件的父 Vue 实例

initLifecycle

之前提到过对子组件的实例化过程先会调用 initInternalComponent(vm, options) 合并 options,把 parent 存储在 vm.$options 中,在 $mount 之前会调用 initLifecycle(vm) 方法

  1. export function initLifecycle (vm: Component) {
  2. const options = vm.$options
  3. // locate first non-abstract parent
  4. let parent = options.parent
  5. if (parent && !options.abstract) {
  6. while (parent.$options.abstract && parent.$parent) {
  7. parent = parent.$parent
  8. }
  9. parent.$children.push(vm) // 把当前vm存储到父实例的$children中
  10. }
  11. vm.$parent = parent // 保留当前vm的父实例
  12. vm.$root = parent ? parent.$root : vm
  13. vm.$children = []
  14. vm.$refs = {}
  15. vm._watcher = null
  16. vm._inactive = null
  17. vm._directInactive = false
  18. vm._isMounted = false
  19. vm._isDestroyed = false
  20. vm._isBeingDestroyed = false
  21. }

在 vm._update 的过程中,把当前的 vm 赋值给 activeInstance,同时通过 const prevActiveInstance = activeInstance 用 prevActiveInstance 保留上一次的 activeInstance。实际上,prevActiveInstance 和当前的 vm 是一个父子关系,当一个 vm 实例完成它的所有子树的 patch 或者 update 过程后,activeInstance 会回到它的父实例,这样就完美地保证了 createComponentInstanceForVnode 整个深度遍历过程中,在实例化子组件的时候能传入当前子组件的父 Vue 实例,并在 _init 的过程中,通过 vm.$parent 把这个父子关系保留

vm.patch

在createPatchFunction方法中,定义在src/core/vdom/patch.js中

  1. // ...
  2. return function patch (oldVnode, vnode, hydrating, removeOnly) {
  3. if (isUndef(vnode)) {
  4. if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
  5. return
  6. }
  7. let isInitialPatch = false
  8. const insertedVnodeQueue = []
  9. if (isUndef(oldVnode)) {
  10. // empty mount (likely as component), create new root element
  11. isInitialPatch = true
  12. createElm(vnode, insertedVnodeQueue)
  13. } else {
  14. const isRealElement = isDef(oldVnode.nodeType)
  15. if (!isRealElement && sameVnode(oldVnode, vnode)) {
  16. // patch existing root node
  17. patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
  18. } else {
  19. if (isRealElement) {
  20. // mounting to a real element
  21. // check if this is server-rendered content and if we can perform
  22. // a successful hydration.
  23. if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
  24. oldVnode.removeAttribute(SSR_ATTR)
  25. hydrating = true
  26. }
  27. if (isTrue(hydrating)) {
  28. if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
  29. invokeInsertHook(vnode, insertedVnodeQueue, true)
  30. return oldVnode
  31. } else if (process.env.NODE_ENV !== 'production') {
  32. warn(
  33. 'The client-side rendered virtual DOM tree is not matching ' +
  34. 'server-rendered content. This is likely caused by incorrect ' +
  35. 'HTML markup, for example nesting block-level elements inside ' +
  36. '<p>, or missing <tbody>. Bailing hydration and performing ' +
  37. 'full client-side render.'
  38. )
  39. }
  40. }
  41. // either not server-rendered, or hydration failed.
  42. // create an empty node and replace it
  43. oldVnode = emptyNodeAt(oldVnode)
  44. }
  45. // replacing existing element
  46. const oldElm = oldVnode.elm
  47. const parentElm = nodeOps.parentNode(oldElm)
  48. // create new node
  49. createElm(
  50. vnode,
  51. insertedVnodeQueue,
  52. // extremely rare edge case: do not insert if old element is in a
  53. // leaving transition. Only happens when combining transition +
  54. // keep-alive + HOCs. (#4590)
  55. oldElm._leaveCb ? null : parentElm,
  56. nodeOps.nextSibling(oldElm)
  57. )
  58. // update parent placeholder node element, recursively
  59. if (isDef(vnode.parent)) {
  60. let ancestor = vnode.parent
  61. const patchable = isPatchable(vnode)
  62. while (ancestor) {
  63. for (let i = 0; i < cbs.destroy.length; ++i) {
  64. cbs.destroy[i](ancestor)
  65. }
  66. ancestor.elm = vnode.elm
  67. if (patchable) {
  68. for (let i = 0; i < cbs.create.length; ++i) {
  69. cbs.create[i](emptyNode, ancestor)
  70. }
  71. // #6513
  72. // invoke insert hooks that may have been merged by create hooks.
  73. // e.g. for directives that uses the "inserted" hook.
  74. const insert = ancestor.data.hook.insert
  75. if (insert.merged) {
  76. // start at index 1 to avoid re-invoking component mounted hook
  77. for (let i = 1; i < insert.fns.length; i++) {
  78. insert.fns[i]()
  79. }
  80. }
  81. } else {
  82. registerRef(ancestor)
  83. }
  84. ancestor = ancestor.parent
  85. }
  86. }
  87. // destroy old node
  88. if (isDef(parentElm)) {
  89. removeVnodes([oldVnode], 0, 0)
  90. } else if (isDef(oldVnode.tag)) {
  91. invokeDestroyHook(oldVnode)
  92. }
  93. }
  94. }
  95. invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
  96. return vnode.elm
  97. }

负责渲染DOM的函数是createElm,参数parentElm是undefined

createElm

如果组件的根节点是个普通元素,那么vm._vnode也是普通的vnode

  1. function createElm (
  2. vnode, // 组件渲染的vnode,也就是vm._vnode
  3. insertedVnodeQueue,
  4. parentElm, // undefined
  5. refElm,
  6. nested,
  7. ownerArray,
  8. index
  9. ) {
  10. if (isDef(vnode.elm) && isDef(ownerArray)) {
  11. // This vnode was used in a previous render!
  12. // now it's used as a new node, overwriting its elm would cause
  13. // potential patch errors down the road when it's used as an insertion
  14. // reference node. Instead, we clone the node on-demand before creating
  15. // associated DOM element for it.
  16. vnode = ownerArray[index] = cloneVNode(vnode)
  17. }
  18. vnode.isRootInsert = !nested // for transition enter check
  19. // 返回值是false
  20. if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
  21. return
  22. }
  23. const data = vnode.data
  24. const children = vnode.children
  25. const tag = vnode.tag
  26. if (isDef(tag)) {
  27. if (process.env.NODE_ENV !== 'production') {
  28. if (data && data.pre) {
  29. creatingElmInVPre++
  30. }
  31. if (isUnknownElement(vnode, creatingElmInVPre)) {
  32. warn(
  33. 'Unknown custom element: <' + tag + '> - did you ' +
  34. 'register the component correctly? For recursive components, ' +
  35. 'make sure to provide the "name" option.',
  36. vnode.context
  37. )
  38. }
  39. }
  40. vnode.elm = vnode.ns
  41. ? nodeOps.createElementNS(vnode.ns, tag)
  42. : nodeOps.createElement(tag, vnode)
  43. setScope(vnode)
  44. /* istanbul ignore if */
  45. if (__WEEX__) {
  46. // in Weex, the default insertion order is parent-first.
  47. // List items can be optimized to use children-first insertion
  48. // with append="tree".
  49. const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
  50. if (!appendAsTree) {
  51. if (isDef(data)) {
  52. invokeCreateHooks(vnode, insertedVnodeQueue)
  53. }
  54. insert(parentElm, vnode.elm, refElm)
  55. }
  56. createChildren(vnode, children, insertedVnodeQueue)
  57. if (appendAsTree) {
  58. if (isDef(data)) {
  59. invokeCreateHooks(vnode, insertedVnodeQueue)
  60. }
  61. insert(parentElm, vnode.elm, refElm)
  62. }
  63. } else {
  64. createChildren(vnode, children, insertedVnodeQueue)
  65. if (isDef(data)) {
  66. invokeCreateHooks(vnode, insertedVnodeQueue)
  67. }
  68. insert(parentElm, vnode.elm, refElm)
  69. }
  70. if (process.env.NODE_ENV !== 'production' && data && data.pre) {
  71. creatingElmInVPre--
  72. }
  73. } else if (isTrue(vnode.isComment)) {
  74. vnode.elm = nodeOps.createComment(vnode.text)
  75. insert(parentElm, vnode.elm, refElm)
  76. } else {
  77. vnode.elm = nodeOps.createTextNode(vnode.text)
  78. insert(parentElm, vnode.elm, refElm)
  79. }
  80. }

先创建一个父节点占位符,然后再遍历所有子 VNode 递归调用 createElm,在遍历的过程中,如果遇到子 VNode 是一个组件的 VNode,则重复本节开始的过程,这样通过一个递归的方式就可以完整地构建了整个组件树