下面的全部解析都以這段代碼爲基準:html
new Vue({
el: "#app",
render: h => h(AppSon)
});
複製代碼
其中 AppSon 就是組件,它是一個對象:vue
const AppSon = {
name: "app-son",
data() {
return {
msg: 123
};
},
render(h) {
return h("span", [this.msg]);
}
};
複製代碼
這樣一段代碼,在 Vue 內部組件化的流程順序:node
$createElement
,其實 render 接受的參數 h 就是this.$createElement
的別名createElement
,作一下參數的整理,就進入下一步_createElement
,比較關鍵的一步,在這個方法裏會判斷組件是span
這樣的 html 標籤,仍是用戶寫的自定義組件。createComponent
,生成組件的 vnode,安裝一些 vnode 的生命週期,返回 vnode其實,render 函數最終返回的就是vnode
。api
調用createElement
方法,第一個參數是 vm 實例自身,剩餘的參數原封不動的透傳。bash
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, ... ) 複製代碼
以後有一個判斷app
if (typeof tag === "string") {
// html標籤流程
} else {
// 組件化流程
vnode = createComponent(tag, data, context, children);
}
複製代碼
createComponent
接受的四個參數就是上文的方法傳進去的dom
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;
}
複製代碼
下面有一個邏輯async
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)
,繼續的去遞歸這個初始化的過程。