下面的所有解析都以這段代碼為基準:
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 内部元件化的流程順序:
-
,其實 render 接受的參數 h 就是$createElement
的别名this.$createElement
-
,做一下參數的整理,就進入下一步createElement
-
,比較關鍵的一步,在這個方法裡會判斷元件是_createElement
這樣的 html 标簽,還是使用者寫的自定義元件。span
-
,生成元件的 vnode,安裝一些 vnode 的生命周期,傳回 vnodecreateComponent
其實,render 函數最終傳回的就是
vnode
。
流程解析
$createElement
調用
createElement
方法,第一個參數是 vm 執行個體自身,剩餘的參數原封不動的透傳。
vm.$createElement = function(a, b, c, d) {
return createElement(vm, a, b, c, d, true);
};
複制代碼
複制
createElement
function createElement (
// 上一步傳進來的vm執行個體,在哪個元件的render裡調用,context就是哪個元件的執行個體。
context,
// 在例子中,就是AppSon這個對象
tag,
// 可以傳入props等交給子元件的選項
data,
// 子元件中間的内容
children,
...
)
複制代碼
複制
之後有一個判斷
if (typeof tag === "string") {
// html标簽流程
} else {
// 元件化流程
vnode = createComponent(tag, data, context, children);
}
複制代碼
複制
createComponent
接受的四個參數就是上文的方法傳進去的
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 函數具體做了什麼呢?
createComponent / 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
這個構造函數的靜态屬性上。
createComponent / installComponentHooks
installComponentHooks
這個方法是為了給 vnode 上加入一些生命周期函數,
其中有一個
init
生命周期,這個周期後面被調用的時候再講解。
createComponent / new VNode
可以看出,主要是生成 vnode 的執行個體,并且指派給
vnode.componentInstance
,并且調用
$mount
方法挂載 dom 節點,注意這個
init
生命周期此時還沒有調用。
到這為止
render
的流程就講完了,現在我們擁有了一個
vnode
節點,它有一些關鍵的屬性
- vnode.componentOptions.Ctor: 上一步
生成的子元件構造函數。extend
- vnode.data.hook: 裡面儲存了
等 vnode 生命周期方法init
- vnode.context: 調用$createElement 的是哪個執行個體,這個 context 就是誰。
$mount
最外層的元件調用了
$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)
,繼續的去遞歸這個初始化的過程。