new Vue() & 组件(二)

108 阅读2分钟

组件 vnode 生成组件 dom 的过程

本节主要简述组件 vnode 生成组件 dom 的过程,部分代码就暂时不上图了!


image.png

接上文生成组件 vnode 后将通过 update() 函数生成组件 dom 并挂在页面上。

vm._update() 作为实例的方法,它被调用的时机有两个:首次渲染 / 数据更新。数据更新是在响应式原理的时候触发,目前只涉及组件首次渲染。

1、vm._update(vnode) 函数接受一个参数 vm._render() 函数生成的组件 vnode,然后执行 prevEl = vm.$el; prevVnode = vm._vnode; vm._vnode = vnode; 为了缓存旧的 dom 对象以及旧的 vnode 对象,同时更新当前实例 vm._vnode 属性的值为新生成的 vnode 对象。

2、接着执行 vm.$el = vm.__patch__(vm.$el, vnode); 函数更新当前 vm.$el 属性的值(新 vnode 对应的新 dom 对象)。接着执行如下图所示内容,当 vm 为组件类型实例时下图成立,vm.$vnode 为当前实例的父实例的 vnode。此时组件生成的 dom 会更新到当前实例的父实例的属性 $el 上。

3、下面重点分析组件执行 patch() 函数的过程。

由于在 patch() 函数内前面部分执行逻辑与 new Vue() & JSX(三) 章节相同,所以流程直接走到执行 createElm(vnode,[],parentElm,refElm) 函数。在 createElm() 函数内由于是组件 vnode 所以直接执行 createComponent(vnode,[],parentElm,refElm) 函数并对该函数的返回值进行判断,若为 true 则直接结束 return 结束函数执行。

createComponent(vnode,[],parentElm,refElm) 函数内由于是组件 vnode,并且在生成组件 vnode 时已经注册钩子函数,所以后续逻辑分为两个流程,如下:

3.1、首先执行 vnode.data.hook.init(vnode) 钩子函数,如下:

此时的组件 vnode.componentInstance 值为空,所以执行下图中的代码生成组件的实例 const child = vnode.componentInstance,然后 child.$mount() 函数挂载子组件。此过程也分为两步如下:

3.1.1、在 createComponentInstanceForVnode() 函数内执行如下图所示内容。由于生成组件 vnode 时保存组件构造函数 Sub(),所以直接执行构造函数 new Sub(options),然后执行 vm._init(options) 函数进行初始化。提示:初始化的续流程逻辑与 new Vue() 基本一致,后续只展示不一致地方做比较。

由于是组件 vnode,所以 options._isComponent 为 true,所以执行 initInternalComponent(vm,options) 函数进行合并属性并生成 vm.options。而此时的 vm.options = mergeOptions(Sub.options, options.parent, options._parentVnode)。由于 vm.$options.el 不存在,所以执行结束,开始执行第二步。

3.1.2、由于组件初始化时没有 el 属性,所以组件自身接管 mount() 过程,即为:child.$mount(undefined,false) 。后续调用 mountComponent() 函数,进而执行 vm.render() 函数生成 vnode 并返回,此时的 vnode 为组件真实的 html 标签生成的 vnode。提示:render() 函数是单文件组件经过 vue-loader 编译生成的,上节简述有对应的图片可查看,render()、patch() 过程与 new Vue() & JSX(二/三) 章节基本一致,后续只展示不一致地方做比较。

vm.render() 函数内 vm.$vnode = _parentVnode; vnode = render(); vnode.parent = _parentVnode; 进行属性绑定。vm.$vnode 表示为当前实例的父实例对应的 vnode 对象,最后返回当前 vnode 对象。

vm.update() 函数内先进行 vm._vnode = vnode; 赋值,所以 vm.$vnodevm._vnode 是父子关系。然后执行 patch() 函数,又回到了第一章的 patch() 函数过程生成真实 dom 并返回赋值 vm.$el = dom; 接着更新 vm.$parent.$el = vm.$el; 满足如下图所示的内容。最后执行结束一步一步的返回到 createComponent(vnode) 函数内的下个流程。

下图中的 vm 为子组件实例,vm.$el 为组件的真实 dom 对象。而 vm.$vnode 为组件 vnode 对象,vm.$parent 为当前子组件实例的父实例。

3.2、最后判断 vnode.componentInstance 属性是否存在

  • vnode.componentInstance 属性为组件 vnode 对应的组件实例。执行组件钩子函数生成组件实例 vm、组件真实 dom 对象。
  • vnode.componentInstance 属性存在,则执行 initComponent(vnode) 函数。在该函数内执行 vnode.elm = vnode.componentInstance.$el 进行赋值,此时的 vnode 为组件 vnodevnode.componentInstance 为对应的子组件实例。执行结束返回到 createComponent(vnode) 函数内继续执行。
  • 然后则将生成的组件 vnode.elm 真实 dom 节点插入到父节点内,然后 return true,返回到 createElm() 函数内,然后 return 返回到 patch() 函数内。

4、最后返回到 patch() 函数内从父节点内删除旧的 dom 节点,完成挂载。最后执行生命周期钩子函数 mount().