学习React过程中,了解到React有一个称为”可中断渲染”的特点,React通过Fiber架构,实现了渲染的暂停与继续。比较好奇,这部分是如何设计的?
这里的中断有几个关键点点:
- 渲染中断让步,不阻塞渲染帧。
- 高优先级中断低优先级任务。
- 通过双缓存机制保证不影响页面效果。
1. Fiber 架构:渲染的底层单元
核心思想
React 将组件树的渲染过程拆解为多个 Fiber 节点(虚拟的轻量级任务单元),每个 Fiber 节点对应一个组件实例或 DOM 节点。Fiber 的本质是一个链表结构,链表是非连续存储的数据结构,通过指针连接,由于节点可以通过指针连接前后节点,所以可以快速的从某个节点打断与恢复遍历。Fiber节点包含以下关键属性:
{
tag: FunctionComponent | ClassComponent | HostComponent (DOM节点) | ...,
type: 'div' | MyComponent, // 组件类型或DOM标签
stateNode: 实际DOM节点或组件实例,
return: 父Fiber,
child: 第一个子Fiber,
sibling: 兄弟Fiber,
alternate: 指向另一棵树上的对应Fiber, // 双缓存的关键!
effectTag: Placement | Update | Deletion, // 标记DOM操作类型
memoizedProps: 上一次渲染的props,
memoizedState: Hook链表或Class组件的state,
}
为什么需要 Fiber?
- 可中断性:传统递归渲染(React 15 及之前)一旦开始就无法中断,可能导致主线程阻塞。
- 增量渲染:Fiber 的链表结构允许按节点逐步处理,随时暂停或恢复。
2. 双缓存机制(Double Buffering)
什么是双缓存?
React 在内存中同时维护两棵 Fiber 树:
- Current Tree:当前屏幕上显示的 UI 对应的 Fiber 树。
- WorkInProgress Tree (WIP):正在构建的新 Fiber 树(完成后会替换 Current Tree)。
工作流程
- 初始化:首次渲染时,没有 Current Tree,直接构建 WIP 树并提交(变为 Current)。
Current Tree: null WIP Tree: A → B → C (构建完成) → 提交后变为 Current Tree - 更新时:
- 从 Current Tree 的根节点克隆出 WIP Tree(通过
alternate指针关联)。 - 在 WIP Tree 上执行协调(Reconciliation)和 Diffing。
- 完成后,WIP Tree 成为新的 Current Tree。
Current Tree: A → B → C WIP Tree: A' → B' → D (更新后) Commit Phase: WIP Tree 替换 Current Tree - 从 Current Tree 的根节点克隆出 WIP Tree(通过
关键点
- 复用 Fiber 节点:通过
alternate指针复用之前的节点,避免重复创建对象。 - 一致性保证:只有完整的 WIP Tree 才会提交,用户不会看到“半渲染”的 UI。
- 失败回滚:如果渲染中断,直接丢弃 WIP Tree,保持 Current Tree 不变。
图示
Current Tree: A (div)
|
B (p)
|
C (span)
WIP Tree: A' (div)
|
B' (p)
|
D (button) ← C被替换为D
提交后,A' → B' → D 成为新的 Current Tree。
3. 时间切片(Time Slicing)
原理
- 将渲染任务拆分为多个 5ms 左右的小块(避免阻塞主线程)。
- 通过 React 自研调度器在浏览器空闲时执行任务。
- 如果超时或更高优先级任务到来,暂停当前渲染,记录断点位置。
4. 调度器(Scheduler)
任务优先级
React 定义了多种优先级(从高到低):
- Immediate:同步任务(如紧急用户输入)。
- UserBlocking:点击、动画等。
- Normal:普通数据更新(默认)。
- Low:非紧急任务(如预加载)。
- Idle:空闲时执行(如日志上报)。
中断逻辑
- 高优先级任务会打断低优先级任务的协调阶段。
- 中断时,React 保存当前 WIP Tree 的进度(通过 Fiber 链表指针)。
5. 并发模式下的完整流程
阶段一:Render Phase(可中断)
- 开始更新:
setState或父组件渲染触发。 - 协调(Reconciliation):
- 深度优先遍历 Fiber 树,调用组件渲染函数。
- 对比新旧 Virtual DOM,标记变更(如
Placement、Update)。 - 可中断点:每个 Fiber 节点处理完后检查时间和优先级。
阶段二:Commit Phase(不可中断)
- DOM 突变:将 WIP Tree 的变更一次性提交到真实 DOM。
- 生命周期调用:执行
componentDidMount、useLayoutEffect等。 - 切换 Current Tree:WIP Tree 变为 Current Tree。
6. 为什么双缓存能保证一致性?
- 隔离性:所有变更先在 WIP Tree 上计算,不会影响当前屏幕。
- 原子性提交:Commit Phase 是同步的,用户看不到中间状态。
- 错误边界:如果协调阶段出错,直接丢弃 WIP Tree,不会破坏 Current Tree。
总结:React 渲染暂停与继续的完整链条
- Fiber 节点 → 2. 双缓存隔离变更 → 3. 时间切片分块执行 → 4. 调度器优先级控制 → 5. 原子提交保证一致性。
这种设计使得 React 能实现:
- 流畅的用户交互:高优先级任务快速响应。
- 高效的渲染:避免不必要的 DOM 操作。
- 健壮性:渲染失败不会导致 UI 崩溃。