写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue 版本 【2.5.17】
如果你觉得排版难看,请点击 下面链接 或者 拉到 下面 关注公众号 也可以吧
【Vue 原理】Component – 源码版 之 创建组件 VNode
今天就要开启我们 Component 探索之旅,旅途有点长,各位请坐好,不要睡着了
内容的主题是,Component 的创建过程,从调用 component,到 component 挂载,到底经历了什么?欢迎来到 component 的内心世界
建议可以先看看白话版
Component – 白话版
Component 创建,我主要分了两个流程
1、创建 组件 VNode
2、挂载 组件 DOM
每个流程涉及源码都很多,所以每个流程写一篇文章。没错了,今天讲的就是 创建组件 VNode
场景设置
首先,我们假定现在有这么一个模板,使用了 test 组件
然后页面噼里啪啦执行到了 准备挂载 DOM 的步骤(之前的部分跟本主题无关,跳过)
然后页面准备执行渲染函数 render,嗯,就是执行上面模板生成的渲染函数,如下
没有错,我们的 Vue 已经走到了这一步,那么我们的突破口是什么?
没错,就是 _c
function _createElement(context, tag, data, children) {
var vnode;
var options = context.$options
// 从父组件选项上拿到 对应的组件的选项
var Ctor = options.components[tag]
if (正常的 HTML 标签) {.... 直接新建 VNode}
else if (Ctor) {
vnode = createComponent(
Ctor, data, context,
children, tag
);
}
return vnode
}
今天讲的是 component,跳过其他,直接走到 第二个 if,嗯,他调用了一个 createComponent
好的,我去前面探探路
function createComponent(
Ctor, data, context,
children, tag
) {
var baseCtor = context.$options._base;
// 创建组件构造函数
Ctor = baseCtor.extend(Ctor);
var vnode = new VNode("vue-component-" + (Ctor.cid) + name,
data, undefined, undefined,
undefined, context,
{Ctor: Ctor}
);
return vnode
}
这个 createComponent 什么鬼的,作用大概是
1、创建组件构造函数
2、处理父组件给子组件的数据
3、创建组件 外壳 VNode
由于处理数据什么的,跟本内容无关,所以其他源码一律去掉,那么就只剩下两个流程
下面就开始这两个流程
创建组件构造函数
上面的源码中有两句话(如下),作用就是为组件创建一个构造函数!
var baseCtor = context.$options._base;
Ctor = baseCtor.extend(Ctor);
看得懂吗?看懂了?好吧,那我就不讲了
算了,算了,还是讲吧,毕竟当时自己也是懵逼的
首先,context 是什么?
context 是执行整个渲染函数的上下文对象,很明显,这里就是页面的 实例 vm 了
那么,$options 是什么?
$options 就是 实例自定义选项 和 全局选项合并之后的 产物
Vue.prototype._init = function(options) {
.....
vm.$options = mergeOptions( // 把两个对象合并
vm.constructor.options,
options, vm
);
.....
}
vm.constructor 是什么?
没错,就是 Vue,你使用 new Vue 创建的页面,构造函数肯定是 Vue 啦
vm.constructor.options 是什么?
看下面
Vue.options = Object.create(null);
Vue.options.components = Object.create(null);
Vue.options.directives = Object.create(null);
Vue.options.filters = Object.create(null);
Vue.options._base = Vue;
Vue 在引入的时候,就完成了很多初始化的内容,这里就是其中给 Vue 增加 options 的部分
你看到的 component 啊,filter 什么的啊,没错,保存的就是你全局注册的 component,filter
然后每个页面都能使用到 全局组件,全局 filter 的原因
就是因为在 页面实例初始化的时候,把 页面选项 和 全局选项私下合并 了
然后,你应该能看到这一句,保存了 Vue 构造函数在 options._base 中
Vue.options._base = Vue;
那么,你应该能理解前面出现的源码了
var baseCtor = context.$options._base;
没错!baseCtor 拿到的就是 Vue!!!!
然后还有一句
Ctor = baseCtor.extend(Ctor);
既然 baseCtor 是 Vue,那 baseCtor.extend 是 Vue.extend?没有错!
正是他!完成了创建组件构造函数的伟大之举!!让我们一起来欣赏下
Vue.extend = function(extendOptions) {
// this 指向 Vue
var Super = this;
var Sub = function VueComponent(options) {this._init(options);
};
// 原型链继承
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
// Super 永远是 Vue,所以这里就是 合并全局选项
// 现在 Super 就是 vue,把 Vue 和 Sub 合并
// 是把一些全局的组件 指令合并到 Sub 中
Sub.options = mergeOptions(
// optios 还包括 mixins 注入的全局
Super.options,
extendOptions
);
return Sub
};
这个函数,会返回一个函数 VueComponent,他就是组件的构造函数!用来下篇文章创建组件实例的!!
上面的源码,做的事,简单说,就是继承父类 Vue,然后合并 options 等
最后,提一下,所有实例的父类构造函数 Super 都是 Vue
并不是说,组件 a 有一个子组件 b,然后组件 b 的父类构造函数就是 a.contructor,这是不对的,永远是 Vue,谁都是 Vue
创建组件外壳 VNode
现在就是前面代码 createComponent 中的最后一步了
注意注意,这里创建的是【组件外壳节点】,内部节点还没有上场,在下篇文章才出现
至于,什么是外壳节点,去看下我的 VNode – 源码版
跳到相关内容看就好了
var vnode = new VNode(
"vue-component-" + Ctor.cid + name,
data, undefined, undefined,
undefined, context,
{Ctor: Ctor}
);
那么这个外壳节点的作用是什么?
1、保存刚创建好的组件构造函数,下篇文章中会调用到
2、保存父组件给子组件 关联的数据,比如 event,props 之类的(由于跟本主题无关,为了整洁,统统去掉了)
总结
Component 创建 外壳节点的流程,总结如下
1、页面渲染函数执行
2、_c(‘test’) 执行
3、createElement 碰到 tag 是一个组件
4、从父组件中,拿到 test 组件的 options,传入 createComponent(作用是创建构造函数和 VNode)
5、createComponent 调用 Vue.extend 创建组件构造函数
6、新建 VNode,并把构造函数和父组件给子组件的数据保存进去
7、返回 VNode