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 用循环代替递归遍历,并用一个变量记住进度,从而支持「暂停—恢复」。双缓存在内存里维护
current和workInProgress两棵树,新树后台建好后一次性切换指针,既避免视觉撕裂,又让未完成的渲染可被随时丢弃。