試試這麼讀preact源碼(一)- createElement/h 函數

以前閱讀的是 preactv8 版本,官方已經更新到 v10 的測試版了,接下來咱們主要對照兩個版本之間的差別來學習源碼,這樣就能清楚的知道做者爲何要這麼改,這麼改有哪些好處了!node

createElement/h 函數

V10preact 學乖了,總算在源碼階段就更貼近 react 了:react

function createElement(type, props, children) {
    //...
}
複製代碼

哈哈,仔細看 createElement 方法的三個參數,類 react,就要從變量名開始!git

形參的不一樣

咱們來看下上個版本的h方法:github

function createElement(nodeName, attributes){
    // ...
}
複製代碼

新版本中新增了一個形參 childrennodeName,attributes 對應 type,props),這個變量的做用就是存儲子組件的,先看下新版本對這個形參的處理:api

if (arguments.length>3) {
	children = [children];
	for (let i=3; i<arguments.length; i++) {
		children.push(arguments[i]);
	}
}
複製代碼

傳入 createElement 方法的參數數目,若是大於3個,則第三個參數 children 從新定義位爲一個數組,原值是這個數組的第一個元素,而後遍歷這個方法第3位以後的全部參數,並做爲元素壓入 children 這個數組中。數組

上個版本的 createElement 方法也是經過遍歷 arguments 來收集子組件的,不過它在函數外定義了一個全局對象和一個初始子組件的空數組:緩存

// 將從第3位起的參數收集起來(其實就是一堆 h 方法的執行函數)
const stack = [];

// 定義一個初始子組件空數組
const EMPTY_CHILDREN = [];

function h(nodeName, attributes) {
    // ...
    // 遍歷h函數的參數,從第三個參數開始就是子組件
    for (i = arguments.length; i-- > 2; ) {
      // 將從第3位起的參數收集起來(其實就是一堆 h方法的執行函數)
      stack.push(arguments[i]);
    }
    // ...
}
複製代碼

最終實現的效果是同樣的,新版本用一個形參代替了函數外的兩個變量,這兩個變量雖然不是全局,但對於這個模塊來講是共用的,這就引出了一個話題:函數的反作用dom

舊版本中,h 函數內部改變了函數體外部的一個變量,在這個函數執行完成以後,這個對象不會被銷燬,並且所指向的值也發生了改變,而這種改變就是 h 函數的反作用。函數

若是這個模塊中另外一個函數也用到了這個變量,那就可能會形成不可預估的bug,因此,這個反作用是能夠避免的。性能

新版本的 h 方法用一個形參從新賦值爲一個數組的操做都是在函數體內部作的,與外部沒有任何聯繫,函數執行完成後,函數體內部的變量都會銷燬,這樣的優化是很是值得咱們學習的。

移除了對子組件的遍歷及類型判斷

在舊版本中,函數內部會對 stack 這個收集子組件的數組元素作類型判斷:

  • boolean ,將元素從新賦值爲 null
  • number ,轉化成 string
  • 值爲 null ,則從新賦值爲空字符

新版本去掉這樣的操做,主要是由於它將子組件的操做放在了 props

if (children != null) {
    props.children = children;
}
複製代碼

後面會講到新版本對 props 專門作了一個模塊及方法 diff/props.js 中的 diffProps 函數。

新增了 createVNode 方法,建立 vnode 對象不在用 new 關鍵字

舊版本的虛擬 dom 是經過實例話 Vnode 類來實現的

const VNode = function VNode() {};

let p = new VNode();
p.nodeName = nodeName;
p.children = children;
p.attributes = attributes == null ? undefined : attributes;
p.key = attributes == null ? undefined : attributes.key;

複製代碼

建立一個虛擬 dom,就要 new 一個對象,學過js的人都聽過,new 是很耗性能的,具體能夠參考這篇文章:prototype, constructor and new

新版本中沒有在 createElement 這個函數內部直接作建立,而是調用了一個 createVNode 函數:

export function createVNode(type, props, key, ref) {
  const vnode = {
    type,
    props,
    key,
    ref,
    _children: null,
    _dom: null,
    _lastDomChild: null,
    _component: null
  };
  vnode._self = vnode;

  if (options.vnode) options.vnode(vnode);

  return vnode;
}
複製代碼

這個函數的職責就是建立一個虛擬dom,首先在函數內部定義了一個字面量 vnode,它有若干的屬性,其中要注意的是以 _ 開頭的都是系統內部會使用到的,這裏只是作了一個初始值:

  • type 虛擬dom的元素類型,如 div span ,一個文本類型,或者是一個 function
  • props 經過jsx傳入的屬性
  • key 惟一鍵值
  • ref 返回虛擬dom的真是dom
  • _children 子組件集合
  • _dom 真實dom
  • _lastDomChild 子組件中的最後一個dom
  • _component 指向的子組件
  • _self 緩存了虛擬dom自己信息

新版本把建立虛擬 dom 獨立成一個小方法供其餘模塊複用這個邏輯,尤爲是在 diff 子組件的時候會遞歸調用。

createElement 函數講完了,其實我以爲這個函數能夠說是 preact 真正的入口函數,在後續會講到 render 的時候就會發現,虛擬 dom 是一切的開始!

原文地址

相關文章
相關標籤/搜索