5.3 组件Vnode渲染真实DOM

有了完整的Vnode tree,接下来是根据Vnode tree渲染真实的DOM

5.3.1 真实节点渲染流程图

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

5.3.2 具体流程分析

    • 经过vm.render()生成完整的Virtual Dom树后,紧接着执行Vnode渲染真实DOM的过程,即vm.update(),而update的核心方法是vm._patch
    • vm.patch内部会通过 createElm去创建真实的DOM元素,期间遇到子Vnode会递归调用createElm方法。
    • 递归调用过程中,判断该节点类型为组件类型是通过createComponent方法判断的,该方法和渲染Vnode阶段的方法createComponent不同,他会调用子组件的init初始化钩子函数,并完成组件的DOM插入。
    • init初始化钩子函数的核心是new实例化这个子组件,实例化子组件的过程又回到合并配置,初始化生命周期,初始化事件中心,初始化渲染的过程
    • 完成所有子组件的实例化和节点挂载后,最后才回到根节点的挂载。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方法中,createComponent的核心是会判断这个Vnode是否为子组件,如果条件满足,则执行组件注册时安装的init方法(由于在组件注册过程中会安装一系列的钩子函数,所以是否有钩子函数可以作为判断组件的唯一条件)。

  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. }

由于前面在介绍组件内部钩子函数时跳过了每个钩子内部实现功能的介绍,所以我们需要回头分析 init钩子函数的执行逻辑(其中忽略keeplive分支逻辑)。

  1. var componentVNodeHooks = {
  2. // 忽略keepAlive过程
  3. var child = vnode.componentInstance = createComponentInstanceForVnode(vnode,activeInstance);
  4. child.$mount(hydrating ? vnode.elm : undefined, hydrating);
  5. }
  6. function createComponentInstanceForVnode(vnode, parent) {
  7. ···
  8. // 实例化Vue子组件实例
  9. return new vnode.componentOptions.Ctor(options)
  10. }

init执行过程中会调用createComponentInstanceForVnode对子组件进行实例化。调用createComponent是一个递归调用实例化所有子组件的过程,只有会将所有的子组件实例化,并挂载到对应父节点上后,才最后进行根节点的挂载。