前言
今天開始看VNode了。
vnode
首先找到vnode定義,在vnode裏面的vnode.js中,Vue的vnode是參考snabdom實現的,這個vnode庫和React的vNode和diff的實現是有一些不同的,這一點之前的文章也有講過,Vue基於這個庫做了一些擴展。
雖然看起來vnode.js的屬性很多,但是其實常用的也就那麼多,如:
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
...
接下來隨着createElement的進入,我們會用到這些屬性。
createElement
上一節講到vm._c和vm.$createElement都調用了createElement方法,上一節簡單看過了這個方法,知道了alwaysNormalize這個參數對後面調用_createElement的影響,這次再細看一下:
export function createElement(
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children;
children = data;
data = undefined;
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE;
}
return _createElement(context, tag, data, children, normalizationType);
}
可以看到這裏使用到了context,即vm實例,tag,即tag,例如’div’,data,可以爲空,之後做了個邏輯判斷,如果data是一個數組或一個基本類型值,這裏做了一個前移操作,把data置爲undefined,其他依次前移。
所以總的來看,createElement其實是對參數做了簡單處理,真正的Vnode實現還是要看_createElement方法。
_createElement
接着又是一層參數判斷:
if (isDef(data) && isDef((data: any).__ob__)) {
這裏判斷傳進來的data是不可以爲響應式的,否則如果在開發環境會爆出警告,同時調用createEmptyVNode方法,從名字就可以看出來,這個vnode沒有任何意義。
接下還是一些類型判斷,如data和data.is不能是null或undefined,data的key不能是基本類型等。
接下來到重點部分,對children進行normalize,主要有兩種:
export function simpleNormalizeChildren (children: any) {
for (let i = 0; i < children.length; i++) {
if (Array.isArray(children[i])) {
return Array.prototype.concat.apply([], children)
}
}
return children
}
這裏目的就是拍成一個一維數組,但是這裏只循環了一遍,即只能拍二維的數組,再深的話就無能爲力了。
另一個:
export function normalizeChildren (children: any): ?Array<VNode> {
return isPrimitive(children)
? [createTextVNode(children)]
: Array.isArray(children)
? normalizeArrayChildren(children)
: undefined
}
代碼很簡單,如果是基本類型,直接創建文本節點放入數組,而文本節點的創建即把tag, data, children都傳值undefined,最後的基本類型直接帶入text;如果是數組,繼續對數組處理,否則返回undefined。
和上一種方法我們大概可以猜到,normalizeArrayChildren其實就是遞歸的拍平數組。事實也確實是這樣的,高於二維的normalizeArrayChildren就可以幫助我們做到拍平數組的功效。
當然其中也有一些優化,如文本節點的合併:
if (isTextNode(c) && isTextNode(last)) {
// merge adjacent text nodes
res[lastIndex] = createTextVNode(last.text + c.text)
}
總之最後就是生成一個一維的vnode數組。
最後回到createElement裏面,判斷tag是不是一個string,如果是的話,判斷是不是原生HTML標籤的string,然後生成對應的vnode,如果不是string,會調用createComponent,這是後話了。