React Fiber 數據結構揭祕

此章節會通過兩個 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 中,節點之間的關係可以用數據結構中的鏈表來表示。

節點之間的鏈表有三種情形, 用圖表示如下:

  1. 父節點到子節點(紅色虛線)
  2. 同層節點(黃色虛線)
  3. 子節點到父節點(藍色虛線)
父節點指向第一個子節點, 每個子節點都指向父節點,同層節點間是單向鏈表。

首先, 構建節點的數據結構, 如下所示:

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 的原因。

相關鏈接

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