FumadocsZDecode
React

Fiber 的链表与双缓存

链表和双缓存是支撑 Fiber「可中断渲染」的两块基石:链表让遍历能用循环代替递归并记住进度,双缓存让新旧两棵树切换时不出现「渲染到一半」的中间态。


一、链表是什么

链表是一种数据结构:每个节点除了存自己的数据,还存一个指针指向下一个节点。节点在内存里不必连续,靠指针串起来。

interface ListNode {
  value: any
  next: ListNode | null // 指针,指向下一个节点
}

与数组对比:

数组链表
内存连续存储分散,靠指针连接
随机访问快(靠下标)慢(要逐个走)
插入/删除慢(要移动元素)快(只改指针)
记录进度靠下标靠节点引用

二、Fiber 为什么用链表

React 15 用递归遍历组件树,递归依赖 JS 调用栈,栈一断进度就丢了,无法暂停后恢复。

Fiber 把树用链表「拍平」,每个节点存三个指针:

interface FiberNode {
  child: FiberNode | null   // 第一个子节点
  sibling: FiberNode | null // 下一个兄弟节点
  return: FiberNode | null  // 父节点
}

遍历规则:有 child 先走 child,没 child 走 sibling,都没有就靠 return 回到父级再找父级的 sibling

关键好处:用一个变量 nextUnit = 当前节点 就能记住遍历到哪。循环被打断时进度保存在这个变量里,下一帧从它继续——这正是可中断渲染的基础

// 用循环代替递归,进度存在 nextUnit
while (nextUnit && deadline.timeRemaining() > 0) {
  nextUnit = performUnitOfWork(nextUnit)
}

三、双缓存是什么

注意名字是双缓存(Double Buffering),借用自图形渲染领域。

问题:一边计算新画面、一边往屏幕上画,用户会看到「画了一半」的撕裂、闪烁。

解法:内存里准备两份。一份正在显示,另一份在后台默默画好,画完一次性切换,用户永远只看到完整画面。游戏/动画里就是这么做的——屏幕显示 buffer A,下一帧在 buffer B 画好后直接切到 B,两个缓冲交替使用。


四、Fiber 里的双缓存

React 在内存中同时维护两棵 Fiber 树

  • current 树:屏幕上正在显示的。
  • workInProgress 树:内存里正在构建的新版本

两棵树的对应节点通过 alternate 指针互相指向:

// 互相引用,便于复用节点对象
currentFiber.alternate === workInProgressFiber

工作流程:

旧的 current 树切换后留作下次复用的缓冲。


五、双缓存的三个好处

好处说明
避免中间态渲染在后台树上进行,用户不会看到构建一半的 UI
可中断/可丢弃workInProgress 没建完被打断可直接丢掉重来,不影响屏幕上的 current
复用省内存两棵树通过 alternate 交替复用节点对象,不必每次重建整棵树

React 18 的并发渲染(Concurrent Rendering)正是建立在双缓存「随时可丢弃重来」的能力之上。


核心两句话:链表用指针把节点串起来,让 React 用循环代替递归遍历,并用一个变量记住进度,从而支持「暂停—恢复」。双缓存在内存里维护 currentworkInProgress 两棵树,新树后台建好后一次性切换指针,既避免视觉撕裂,又让未完成的渲染可被随时丢弃。

On this page