5.3 组件Vnode渲染真实DOM

根据前面的分析,不管是全局注册的组件还是局部注册的组件,组件并没有进行实例化,那么组件实例化的过程发生在哪个阶段呢?我们接着看Vnode tree渲染真实DOM的过程。

5.3.1 真实节点渲染流程图

5.3 组件Vnode渲染真实DOM - 图1

5.3.2 具体流程分析

  1. 经过vm._render()生成完整的Virtual Dom树后,紧接着执行Vnode渲染真实DOM的过程,这个过程是vm.update()方法的执行,而其核心是vm.__patch__
  2. vm.__patch__内部会通过 createElm去创建真实的DOM元素,期间遇到子Vnode会递归调用createElm方法。
  3. 递归调用过程中,判断该节点类型是否为组件类型是通过createComponent方法判断的,该方法和渲染Vnode阶段的方法createComponent不同,他会调用子组件的init初始化钩子函数,并完成组件的DOM插入。
  4. init初始化钩子函数的核心是new实例化这个子组件并将子组件进行挂载,实例化子组件的过程又回到合并配置,初始化生命周期,初始化事件中心,初始化渲染的过程。实例挂载又会执行$mount过程。
  5. 完成所有子组件的实例化和节点挂载后,最后才回到根节点的挂载。

__patch__核心代码是通过createElm创建真实节点,当创建过程中遇到子vnode时,会调用createChildren,createChildren的目的是对子vnode递归调用createElm创建子组件节点。

  1. // 创建真实dom
  2. function createElm (vnode,insertedVnodeQueue,parentElm,refElm,nested,ownerArray,index) {
  3. ···
  4. // 递归创建子组件真实节点,直到完成所有子组件的渲染才进行根节点的真实节点插入
  5. if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
  6. return
  7. }
  8. ···
  9. var children = vnode.children;
  10. //
  11. createChildren(vnode, children, insertedVnodeQueue);
  12. ···
  13. insert(parentElm, vnode.elm, refElm);
  14. }
  15. function createChildren(vnode, children, insertedVnodeQueue) {
  16. for (var i = 0; i < children.length; ++i) {
  17. // 遍历子节点,递归调用创建真实dom节点的方法 - createElm
  18. createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
  19. }
  20. }

createComponent方法会对子组件Vnode进行处理中,还记得在Vnode生成阶段为子Vnode安装了一系列的钩子函数吗,在这个步骤我们可以通过是否拥有这些定义好的钩子来判断是否是已经注册过的子组件,如果条件满足,则执行组件的init钩子。

init钩子做的事情只有两个,实例化组件构造器,执行子组件的挂载流程。(keep-alive分支看具体的文章分析)

  1. function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  2. var i = vnode.data;
  3. // 是否有钩子函数可以作为判断是否为组件的唯一条件
  4. if (isDef(i = i.hook) && isDef(i = i.init)) {
  5. // 执行init钩子函数
  6. i(vnode, false /* hydrating */);
  7. }
  8. ···
  9. }
  10. var componentVNodeHooks = {
  11. // 忽略keepAlive过程
  12. // 实例化
  13. var child = vnode.componentInstance = createComponentInstanceForVnode(vnode,activeInstance);
  14. // 挂载
  15. child.$mount(hydrating ? vnode.elm : undefined, hydrating);
  16. }
  17. function createComponentInstanceForVnode(vnode, parent) {
  18. ···
  19. // 实例化Vue子组件实例
  20. return new vnode.componentOptions.Ctor(options)
  21. }

显然Vnode生成真实DOM的过程也是一个不断递归创建子节点的过程,patch过程如果遇到子Vnode,会优先实例化子组件,并且执行子组件的挂载流程,而挂载流程又会回到_render,_update的过程。在所有的子Vnode递归挂载后,最终才会真正挂载根节点。