試試這麼讀preact原始碼(一)- createElement/h 函式
V10
的preact
學乖了,總算在原始碼階段就更貼近react
了:
function createElement(type, props, children) { //... } 複製程式碼
哈哈,仔細看createElement
方法的三個引數,類react
,就要從變數名開始!
形參的不同
我們來看下上個版本的h方法:
function createElement(nodeName, attributes){ // ... } 複製程式碼
新版本中新增了一個形參children
(nodeName
,attributes
對應type
,props
),這個變數的作用就是儲存子元件的,先看下新版本對這個形參的處理:
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]); } // ... } 複製程式碼
最終實現的效果是一樣的,新版本用一個形參代替了函式外的兩個變數,這兩個變數雖然不是全域性,但對於這個模組來說是共用的,這就引出了一個話題:函式的副作用 。
舊版本中,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
是一切的開始!