此章節會通過兩個 demo
來展示 Stack Reconciler
以及 Fiber Reconciler
的資料結構。
首先用程式碼表示上圖節點間的關係。比如 a1 節點
下有 b1、b2、b3 節點
, 就可以把它們間的關係寫成 a1.render = () => [b1, b2, b3]
;
var a1 = { name: 'a1', render = () => [b1, b2, b3] }
var b1 = { name: 'b1', render = () => [c1] }
var b2 = { name: 'b2', render = () => [c2] }
var b3 = { name: 'b3', render = () => [] }
var c1 = { name: 'c1', render = () => [d1] }
var c2 = { name: 'c2', render = () => [] }
var d1 = { name: 'd1', render = () => [d2] }
var d2 = { name: 'd2', render = () => [] }
複製程式碼
Stack Reconciler
在 React 16
之前,節點之間的關係可以用資料結構中樹的深度遍歷
來表示。
如下實現 walk
函式, 將深度遍歷的節點列印出來。
walk(a1)
function walk(instance) {
if (!instance) return
console.log(instance.name)
instance.render().map(walk)
}
複製程式碼
輸出結果為: a1 b1 c1 d1 d2 b2 c2 b3
Fiber Reconciler
在 React 16
中,節點之間的關係可以用資料結構中的連結串列
來表示。
節點之間的連結串列有三種情形, 用圖表示如下:
- 父節點到子節點(紅色虛線)
- 同層節點(黃色虛線)
- 子節點到父節點(藍色虛線)
父節點指向第一個子節點, 每個子節點都指向父節點,同層節點間是單向連結串列。
首先, 構建節點的資料結構, 如下所示:
var FiberNode = function(instance) {
this.instance = instance
this.parent = null
this.sibling = null
this.child = null
}
複製程式碼
然後建立一個將節點串聯起來的 connect
函式:
var connect = function(parent, childList) {
parent.child = childList.reduceRight((prev, current) => {
const fiberNode = new FiberNode(current)
fiberNode.parent = parent
fiberNode.sibling = prev
return fiberNode
}, null)
return parent.child
}
複製程式碼
在 JavaScript 中實現連結串列的資料結構可以巧用 reduceRight
connect
函式中實現了上述連結串列關係。可以像這樣使用它:
var parent = new FiberNode(a1)
var childFirst = connect(parent, a1.render())
複製程式碼
這樣子便完成了 a1 節點
指向 b1 節點
的連結串列、b1、b2、b3 節點間
的單向連結串列以及 b1、b2、b3 節點
指向 a1 節點
的連結串列。
最後剩下 goWalk
函式將全部節點給遍歷完。
// 列印日誌以及新增列表
var walk = function(node) {
console.log(node.instance.name)
const childLists = node.instance.render()
let child = null
if (childLists.length > 0) {
child = connect(node, childLists)
}
return child
}
var goWalk = function(root) {
let currentNode = root
while (true) {
const child = walk(currentNode)
// 如果有子節點
if (child) {
currentNode = child
continue
}
// 如果沒有相鄰節點, 則返回到父節點
while (!currentNode.sibling) {
currentNode = currentNode.parent
if (currentNode === root) {
return
}
}
// 相鄰節點
currentNode = currentNode.sibling
}
}
// 呼叫
goWalk(new FiberNode(a1))
複製程式碼
列印結果為 a1 b1 c1 d1 d2 b2 c2 b3
Fiber Reconciler 的優勢
通過分析上述兩種資料結構實現的程式碼,可以得出下面結論:
- 基於樹的深度遍歷實現的 Reconciler: 一旦進入呼叫棧便無法暫停;
- 基於連結串列實現的 Reconciler: 在
while(true) {}
的迴圈中, 可以通過currentNode
的賦值重新得到需要操作的節點,而在賦值之前便可以'暫停'來執行其它邏輯, 這也是requestIdleCallback
能得以在Fiber Reconciler
的原因。