五 Vue 原理解析之 虚拟Dom 到真实Dom的转换过程( 三 )


组件VNode生成Dom
{// 组件VNodetag: 'vue-component-1-app',context: {...},componentOptions: {Ctor: function(){...},// 子组件构造函数propsData: undefined,children: undefined,tag: undefined},data: {on: undefined,// 原生事件hook: {// 组件钩子init: function(){...},insert: function(){...},prepatch: function(){...},destroy: function(){...}}}}-------------------------------------------// app组件内模板app text
首先看张简易流程图,留个影响即可,方便理清之后的逻辑顺序(这里借用网上一张图):
使用上一章组件生成VNode,看下在 内创建组件Dom分支逻辑是怎么样的:
function createElm(vnode, insertedVnodeQueue, parentElm, refElm) { ...if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { // 组件分支return}...
执行 方法,如果是元素节点不会返回任何东西,所以是,会继续走接下来的创建元节点的逻辑 。现在是组件,我们看下 的实现:
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {let i = vnode.dataif(isDef(i)) {if(isDef(i = i.hook) && isDef(i = i.init)) {i(vnode)// 执行init方法}...}}
首先会将组件的vnode.data赋值给i, 是否有这个属性就能判断是否是组件vnode 。之后的if(isDef(i = i.hook) && isDef(i = i.init)) 集判断和赋值为一体,if 内的i(vnode) 就是执行的组件init(vnode)方法 。这个时候我们来看下组件的init 钩子方法做了什么:
import activeInstance// 全局变量const init = vnode => {const child = vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance)...}
是一个全局的变量,再 方法内赋值为当前实例,再当前实例做 patch 的过程中作为了组件的父实例传入,在子组件的时构建组件关系 。将 执行的结果赋值给了, 所以看下它的返回的结果是什么:
exportcreateComponentInstanceForVnode(vnode, parent) {// parent为全局变量activeInstanceconst options = {// 组件的options_isComponent: true,// 设置一个标记位,表明是组件_parentVnode: vnode, parent// 子组件的父vm实例,让初始化initLifecycle可以建立父子关系}return new vnode.componentOptions.Ctor(options)// 子组件的构造函数定义为Ctor}
再组件的init 方法内首先执行方法,这个方法的内部就会将子组件的构造函数实例化,因为子组件的构造函数继承了基类Vue的所有能力,这个时候相当于执行new Vue({…}),接下来又会执行==_init方法进行一系列的子组件的初始化逻辑,回到_init== 方法内,因为他们之间还是有些不同的地方:
Vue.prototype._init = function(options) {if(options && options._isComponent) {// 组件的合并options,_isComponent为之前定义的标记位initInternalComponent(this, options)// 区分是因为组件的合并项会简单很多}initLifecycle(vm)// 建立父子关系...callHook(vm, 'created')if (vm.$options.el) { // 组件是没有el属性的,所以到这里咋然而止vm.$mount(vm.$options.el)}}----------------------------------------------------------------------------------------function initInternalComponent(vm, options) {// 合并子组件optionsconst opts = vm.$options = Object.create(vm.constructor.options)opts.parent = options.parent// 组件init赋值,全局变量activeInstanceopts._parentVnode = options._parentVnode// 组件init赋值,组件的vnode ...}
前面都还是执行的好好的,最后却因为没有el属性,所以没有挂载,方法执行完毕 。这个时候我们回到组件的init方法,补全剩下的逻辑:
const init = vnode => {const child = vnode.componentInstance = // 得到组件的实例createComponentInstanceForVnode(vnode, activeInstance)child.$mount(undefined)// 那就手动挂载呗}
我们在init 方法内手动挂载这个组件,接着又会执行组件的==()== 方法得到组件内元素节点VNode , 然后执行vm.(), 执行组件的 patch 方法,因为 $mount 方法传入的是 ,也是 , 会执行 内的这段逻辑: