Vue.js源碼分析(四)--createElement

前言

今天開始看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,這是後話了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章