
下面的所有解析都以这段代码为基准:
new Vue({
el: "#app",
render: h => h(AppSon)
});其中 AppSon 就是组件,它是一个对象:
const AppSon = {
name: "app-son",
data() {
return {
msg: 123
};
},
render(h) {
return h("span", [this.msg]);
}
};这样一段代码,在 Vue 内部组件化的流程顺序:
createElement,做一下参数的整理,就进入下一步_createElement,比较关键的一步,在这个方法里会判断组件是span这样的 html 标签,还是用户写的自定义组件。createComponent,生成组件的 vnode,安装一些 vnode 的生命周期,返回 vnode其实,render 函数最终返回的就是vnode。
调用createElement方法,第一个参数是 vm 实例自身,剩余的参数原封不动的透传。
vm.$createElement = function(a, b, c, d) {
return createElement(vm, a, b, c, d, true);
};function createElement (
// 上一步传进来的vm实例,在哪个组件的render里调用,context就是哪个组件的实例。
context,
// 在例子中,就是AppSon这个对象
tag,
// 可以传入props等交给子组件的选项
data,
// 子组件中间的内容
children,
...
)之后有一个判断
if (typeof tag === "string") {
// html标签流程
} else {
// 组件化流程
vnode = createComponent(tag, data, context, children);
}createComponent接受的四个参数就是上文的方法传进去的
function createComponent(
// 还是上文中的tag,本文中是AppSon对象
Ctor,
// 下面的都一致
data,
context,
children
) {
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}
// 给vnode安装一些生命周期函数(注意这里是vnode的生命周期,而不是created那些组件声明周期)
installComponentHooks(data);
var vnode = new VNode(
"vue-component-" + Ctor.cid + (name ? "-" + name : ""),
data,
undefined,
undefined,
undefined,
context,
{
Ctor: Ctor,
propsData: propsData,
listeners: listeners,
tag: tag,
children: children
},
asyncFactory
);
return vnode;
}下面有一个逻辑
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}其中baseCtor.extend(Ctor)就可以暂时理解为 Vue.extend,这是一个全局共用方法,从名字也可以看出它主要是做一些继承,让子组件的也拥有父组件的一些能力,这个方法返回的是一个新的构造函数。
组件对象最终都会用 extend 这个 api 变成一个组件构造函数,这个构造函数继承了父构造函数 Vue 的一些属性
extend 函数具体做了什么呢?
Vue.extend = function(extendOptions) {
extendOptions = extendOptions || {};
// this在这个例子其实就是Vue。
var Super = this;
// Appson这个组件的构造函数
var Sub = function VueComponent(options) {
// 这个_init就是调用的Vue.prototype._init
this._init(options);
};
// 把Vue.prototype生成一个
// { __proto__: Vue.prototype }这样的对象,
// 直接赋值给子组件构造函数的prototype
// 此时子组件构造函数的原型链上就可以拿到Vue的原型链的属性了
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
// 合并Vue.option上的一些全局配置
Sub.options = mergeOptions(Super.options, extendOptions);
Sub["super"] = Super;
// 拷贝静态函数
Sub.extend = Super.extend;
Sub.mixin = Super.mixin;
Sub.use = Super.use;
// 返回子组件的构造函数
return Sub;
};到了这一步,我们一开始定义的 Appson 组件对象,已经变成了一个函数,可以通过 new AppSon()来生成一个组件实例了,并且组件配置对象被合并到了Sub.options这个构造函数的静态属性上。
installComponentHooks这个方法是为了给 vnode 上加入一些生命周期函数,
其中有一个init生命周期,这个周期后面被调用的时候再讲解。
可以看出,主要是生成 vnode 的实例,并且赋值给vnode.componentInstance,并且调用$mount方法挂载 dom 节点,注意这个init生命周期此时还没有调用。
到这为止render的流程就讲完了,现在我们拥有了一个vnode节点,它有一些关键的属性
extend生成的子组件构造函数。init等 vnode 生命周期方法最外层的组件调用了$mount后,组件在初次渲染的时候其实是递归去调用createElm的,而createElm中会去调用组件 vnode 的init钩子。
if (isDef((i = i.hook)) && isDef((i = i.init))) {
i(vnode);
}然后就会走进 vnode 的init生命周期的逻辑
const child = (vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
));
child.$mount(vnode.elm);createComponentInstanceForVnode:
createComponentInstanceForVnode (
vnode: any,
parent: any,
): Component {
const options: InternalComponentOptions = {
// 标记这是一个组件节点
_isComponent: true,
// Appson组件的vnode
_parentVnode: vnode,
// 当前正在活跃的父组件实例,在本例中就是根Vue实例
// new Vue({
// el: "#app",
// render: h => h(AppSon)
// });
parent
}
return new vnode.componentOptions.Ctor(options)
}可以看出,最终调用组件构造函数,然后调用\_init 方法,它接受到的 options 不再是
{
data() {
},
props: {
},
methods() {
}
}这样的传统 Vue 对象了,而是
{
_isComponent: true,
_parentVnode: vnode,
parent,
}这样的一个对象,然后_init 内部会针对这样特征的对象,调用initInternalComponent做一些特殊的处理, 这里有一个疑惑点,那刚刚子组件声明的 data 那些选项哪去了呢? 其实是被保存在Ctor.options里了。
然后在initInternalComponent中,把子组件构造函数上保存的 options 再转移到vm.$options.__proto__上。
var opts = (vm.$options = Object.create(vm.constructor.options));之后生成了子组件的实例后,又会调用child.$mount(vnode.elm),继续的去递归这个初始化的过程。