Appearance
React 的 Fiber 架构主要是为了解决什么问题?
React Fiber 架构:它到底解决了什么问题?
在 React 的世界里,我们常常听到 Fiber 这个词,它通常与"性能优化"、"异步渲染"等概念一同出现。但 Fiber 架构的诞生,其核心目标究竟是为了解决什么问题?
要理解 Fiber 的"为什么",我们首先需要回到它诞生之前的世界,看看那时的 React 面临着怎样的挑战。
Fiber 诞生前的世界:同步渲染的挑战
在 React 16 之前,React 使用一种名为"堆栈调和器"(Stack Reconciler)的机制来更新 UI。当组件的状态发生变化时,React 会启动一个调和(Reconciliation)过程,计算出新的 Virtual DOM,并与旧的进行比较(Diffing),最后将差异更新到真实的 DOM 上。
这个过程最大的特点是:同步且不可中断。
我们可以将它想象成一个电话通话:一旦通话开始,就必须等到通话结束才能去做别的事情。如果这次通话(渲染任务)时间很长——比如需要更新一个包含成百上千个组件的复杂列表——主线程就会被长时间占用。
这时,浏览器就无法响应其他更高优先级的任务,例如:
- 用户输入:用户在输入框里打字,却迟迟看不到字符出现。
- 动画效果:页面上的 CSS 动画或滚动效果会变得卡顿,甚至完全停止。
对于用户来说,直观的感受就是页面卡死、无响应。这种糟糕的体验在日益复杂的 Web 应用中是不可接受的。核心矛盾在于,单一的主线程上,冗长的 JavaScript 计算任务阻塞了更高优先级的用户交互和页面渲染。
Fiber 的核心思想:化整为零,可中断、可恢复
为了解决同步渲染带来的阻塞问题,React 团队设计了全新的调和引擎——Fiber Reconciler。
Fiber 的核心思想非常直观:将一个庞大的更新任务,拆解成许多微小的工作单元。
React 不再一口气完成整个渲染任务,而是将工作分配到一个个"fiber"节点上。每个 fiber 都可以看作是一个轻量化的工作单元。React 可以在完成一个工作单元后,暂停下来,将主线程的控制权交还给浏览器。浏览器可以利用这个间隙去处理更高优先级的任务(比如响应用户输入)。处理完之后,React 会在下一次浏览器空闲时,从上次暂停的地方继续执行下一个工作单元。
这个过程就像一位厨师在准备一桌复杂的宴席:
- 旧模式(同步渲染):厨师必须从头到尾做完一道大菜,中途不能停。如果此时有客人想喝水,只能等着。
- 新模式(Fiber):厨师将做菜过程分解为"洗菜"、"切菜"、"配料"等多个小步骤。每完成一小步,他都会抬头看看有没有客人有紧急需求。如果有,就先去倒杯水,然后再回来继续下一步。
这种"化整为零、可中断、可恢复"的机制,是 Fiber 架构的精髓。它将渲染过程从一个"必须一次性完成"的任务,变成了一个"可以按优先级调度"的任务。
为了实现这一点,React 内部将更新过程划分为两个阶段:
渲染/调和阶段(Render/Reconciliation Phase):这是一个可中断的阶段。在此阶段,React 会构建出"工作正在进行中"的 fiber 树(work-in-progress tree),计算出所有节点的变更。由于这个过程可能会被暂停、重做甚至丢弃,所以它不会产生任何用户可见的副作用(比如操作 DOM)。
提交阶段(Commit Phase):这是一个不可中断的同步阶段。一旦调和阶段完成,React 就会进入提交阶段,将计算出的所有变更一次性应用到 DOM 上。这个过程必须是同步的,以确保 UI 的一致性,避免用户看到渲染了一半的、不完整的界面。
Fiber 究竟解决了哪些关键问题?
综上所述,我们可以清晰地归纳出 Fiber 架构主要解决的问题:
1. 增量渲染,避免主线程阻塞
这是最核心的一点。通过将渲染任务拆分为小块并分散到浏览器的多个帧(frame)中执行,Fiber 极大地减少了单次 JavaScript 执行时间,避免了主线程被长时间霸占,从而让浏览器有时间响应用户操作和执行动画,使应用保持流畅。
2. 实现更新的优先级调度
既然任务可以被中断,那么"接下来该执行哪个任务"就成了一个可以被管理的事情。Fiber 架构使得 React 能够为不同类型的更新赋予不同的优先级。例如,用户输入事件(如打字)的优先级最高,需要立即响应;而动画更新的优先级次之;网络请求返回的数据更新优先级则可以更低。这种优先级调度确保了最关键的交互能得到最及时的处理。
3. 为并发模式(Concurrent Mode)等新特性铺平道路
Fiber 本身是一种底层架构,它为 React 未来的诸多新特性提供了可能性。我们今天熟知的 Suspense(用于数据获取)、useTransition 和 useDeferredValue 等并发特性,都建立在 Fiber 架构可中断和可调度的能力之上。没有 Fiber,这些精细化的渲染控制能力便无从谈起。
fiber是如何实现任务中断/恢复/优先级调度的
一句话核心
Fiber 把原来一次性跑完的递归 Diff,改成了基于 "链表 + 指针" 的循环遍历,每执行一个节点就可以暂停,靠指针记住位置,从而实现中断、恢复、优先级调度。
一、老架构(Stack Reconciler)为什么不行?
React 16 以前是递归 Diff:
- 从根组件开始,递归遍历所有子组件
- 一旦开始,就必须一口气跑完
- 层级深时,主线程阻塞超过 16ms → 页面卡顿、掉帧
问题核心:JS 是单线程,递归不可中断!
二、Fiber 架构的核心设计:把递归 → 链表循环
Fiber 做了一件事:把虚拟 DOM 树,改成一棵链式存储的 Fiber 树,每个节点记录 3 个指针:
typescript
type Fiber = {
// 树结构
child: Fiber | null // 第一个子节点
sibling: Fiber | null // 兄弟节点
return: Fiber | null // 父节点
// 任务调度
pendingProps: any
memoizedState: any // Hooks 链表挂在这里
lanes: Lanes // 优先级
alternate: Fiber | null // 双缓存树
}树结构示例:
A(return: null)
↓ child
B(return: A) → C(sibling: D) → D
↓ child
E遍历方式变成:循环 + 指针移动,而不是递归调用栈。
三、Fiber 如何实现【中断】?
中断本质: 停止循环,让出主线程,不销毁当前遍历指针。
中断条件
React 每处理完一个 Fiber 节点,都会检查时间:
javascript
if (shouldYield()) {
// 主线程忙、超过时间片
pauseRender() // 中断
return
}中断时保留什么?
只保留 2 个东西:
- 当前遍历到哪个 Fiber 节点(指针)
- 当前任务的优先级
栈信息不用保留!因为不是递归。所以中断成本极低。
四、Fiber 如何实现【恢复】?
恢复 = 从上次中断的 Fiber 节点指针继续遍历
流程:
- 浏览器空闲时(
requestIdleCallback) - React 拿到控制权
- 从
lastFiber继续执行 - 走
child → sibling → return遍历
因为 Fiber 树是静态链表结构,不会丢失。
💡 这就是为什么 Hooks 必须用链表: 恢复渲染时,必须能从中间某个 Hook 继续,而不是从头再来。
五、Fiber 如何实现【优先级调度 & 插队】?
这是 React 18 Concurrent Mode 核心:Lane 优先级模型
1. 每个 Fiber 有优先级:lanes
javascript
const lanes = {
NoLanes: 0,
SyncLane: 1, // 同步最高优先级(input输入、点击)
InputContinuousLane: 2,
DefaultLanes: 4, // 正常渲染
IdleLane: 8 // 最低优先级
}2. 高优先级任务可以直接"插队"
场景示例:
- 正在渲染一个长列表(Default 优先级)
- 用户点击按钮/输入框(Sync 优先级)
流程:
- 中断当前低优先级任务
- 立即执行高优先级任务
- 高优完成后,再恢复低优任务
3. 如何做到?
- 每个更新都带优先级
- 调度器始终选择最高优先级任务先跑
- 低优任务可以被放弃 + 重新从头执行
六、Fiber 双缓存树:current / workInProgress
为了支持中断/恢复/插队,React 用了双缓冲 Fiber 树:
| 树类型 | 说明 |
|---|---|
| current Fiber 树 | 真实 DOM 对应的树,展示在页面上 |
| workInProgress 树 | 正在构建的新树,中断时保留中间状态 |
更新时:
- 只修改
workInProgress - 完成后,用它替换
current - 中断不影响页面显示
💡 这就是可中断更新不会导致页面半渲染的原因。
七、整体调度流程(最精炼版)
产生更新(setState、用户输入)
↓
分配优先级(Lanes)
↓
进入调度队列
↓
调度器选出最高优先级任务
↓
循环遍历 Fiber 链表
↓
每完成一个节点,判断是否需要中断
├─ 中断 → 保存指针 → 释放线程
├─ 恢复 → 从指针继续
└─ 完成 → 提交 DOM(commit 阶段,一次性更新,不可中断)八、最核心的 3 句话总结
- Fiber 把递归 Diff 改成链表循环遍历,靠指针记录位置,实现中断恢复。
- 中断只保留当前 Fiber 指针,不保留调用栈,极轻量。
- Lane 优先级 + 双缓存树,让高优任务可以随时打断低优任务。
九、面试满分回答
问:Fiber 如何实现中断、恢复、优先级调度?
答:
React Fiber 将虚拟 DOM 构造成链式结构的 Fiber 树,通过 child、sibling、return 三个指针实现循环遍历,替代了原有的递归遍历。
每处理一个 Fiber 节点后,会判断主线程是否繁忙,若需要则中断任务,保留当前遍历指针,等浏览器空闲后再从指针位置恢复执行。
同时通过 Lane 优先级模型标记任务优先级,调度器优先执行高优先级任务(如输入、点击),低优先级任务可被中断甚至重新执行,配合双缓存 Fiber 树保证 UI 不会出现中间状态。
总结
我们可以认为,React Fiber 架构的出现,标志着 React 的关注点从"如何进行更新"演进到了"如何调度更新"。它从根本上改变了 React 的工作方式,将一个宏大的、不可打断的渲染任务,重塑为一个可拆分、可暂停、可调度、可赋予优先级的协作式系统。
最终,Fiber 解决的并不仅仅是一个技术瓶颈,而是关乎用户体验的根本问题:如何确保应用在持续进行复杂计算的同时,依然能够保持流畅的交互和视觉响应。这使得 React 能够在构建更加复杂、功能更加强大的应用时,依然能为用户提供卓越的体验。