Skip to content

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 内部将更新过程划分为两个阶段:

  1. 渲染/调和阶段(Render/Reconciliation Phase):这是一个可中断的阶段。在此阶段,React 会构建出"工作正在进行中"的 fiber 树(work-in-progress tree),计算出所有节点的变更。由于这个过程可能会被暂停、重做甚至丢弃,所以它不会产生任何用户可见的副作用(比如操作 DOM)。

  2. 提交阶段(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 节点指针继续遍历

流程:

  1. 浏览器空闲时(requestIdleCallback
  2. React 拿到控制权
  3. lastFiber 继续执行
  4. 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 优先级)

流程:

  1. 中断当前低优先级任务
  2. 立即执行高优先级任务
  3. 高优完成后,再恢复低优任务

3. 如何做到?

  • 每个更新都带优先级
  • 调度器始终选择最高优先级任务先跑
  • 低优任务可以被放弃 + 重新从头执行

六、Fiber 双缓存树:current / workInProgress

为了支持中断/恢复/插队,React 用了双缓冲 Fiber 树:

树类型说明
current Fiber 树真实 DOM 对应的树,展示在页面上
workInProgress 树正在构建的新树,中断时保留中间状态

更新时:

  • 只修改 workInProgress
  • 完成后,用它替换 current
  • 中断不影响页面显示

💡 这就是可中断更新不会导致页面半渲染的原因。


七、整体调度流程(最精炼版)

产生更新(setState、用户输入)

分配优先级(Lanes)

进入调度队列

调度器选出最高优先级任务

循环遍历 Fiber 链表

每完成一个节点,判断是否需要中断
    ├─ 中断 → 保存指针 → 释放线程
    ├─ 恢复 → 从指针继续
    └─ 完成 → 提交 DOM(commit 阶段,一次性更新,不可中断)

八、最核心的 3 句话总结

  1. Fiber 把递归 Diff 改成链表循环遍历,靠指针记录位置,实现中断恢复。
  2. 中断只保留当前 Fiber 指针,不保留调用栈,极轻量。
  3. Lane 优先级 + 双缓存树,让高优任务可以随时打断低优任务。

九、面试满分回答

问:Fiber 如何实现中断、恢复、优先级调度?

答:

React Fiber 将虚拟 DOM 构造成链式结构的 Fiber 树,通过 childsiblingreturn 三个指针实现循环遍历,替代了原有的递归遍历。

每处理一个 Fiber 节点后,会判断主线程是否繁忙,若需要则中断任务,保留当前遍历指针,等浏览器空闲后再从指针位置恢复执行。

同时通过 Lane 优先级模型标记任务优先级,调度器优先执行高优先级任务(如输入、点击),低优先级任务可被中断甚至重新执行,配合双缓存 Fiber 树保证 UI 不会出现中间状态。

总结

我们可以认为,React Fiber 架构的出现,标志着 React 的关注点从"如何进行更新"演进到了"如何调度更新"。它从根本上改变了 React 的工作方式,将一个宏大的、不可打断的渲染任务,重塑为一个可拆分、可暂停、可调度、可赋予优先级的协作式系统。

最终,Fiber 解决的并不仅仅是一个技术瓶颈,而是关乎用户体验的根本问题:如何确保应用在持续进行复杂计算的同时,依然能够保持流畅的交互和视觉响应。这使得 React 能够在构建更加复杂、功能更加强大的应用时,依然能为用户提供卓越的体验。

基于 VitePress 的本地知识库