梳理 React Fiber

8 阅读11分钟

截屏2026-05-12 09.36.54.png

React Fiber:重构 React 的渲染引擎

如果你只把 React 当作一个 UI 库来用,你可能永远不会关心 Fiber。但如果你想构建一个流畅的、高交互的现代 Web 应用,理解 Fiber 是在理解 React 为何能同时做到“声明式”与“高性能”

协调器渲染器工作循环 正是 Fiber 架构的三大支柱。下面我将从“Fiber 是什么?解决了什么问题?”开始,深入 Fiber 的本质、工作流程,以及它如何支撑起 Concurrent Mode(并发模式)。


1. 背景:旧调和器(Stack Reconciler)的“不可中断”之痛

在 React 15 及之前,调和器(Stack Reconciler)采用递归同步的方式遍历虚拟 DOM 树。这个过程就像函数调用栈:一旦开始渲染,就会一直递归到最深层的子节点,然后再返回。整个更新过程无法被中断

这意味着:当组件树很庞大时,主线程会被 React 的渲染工作长时间占用,阻塞浏览器主线程。此时用户的点击、输入、动画等交互无法得到响应,导致掉帧、卡顿,甚至浏览器假死。

核心问题:旧的渲染模型是“同步的、递归的、不可中断的”。


2. Fiber 是什么?

Fiber 是 React 16 引入的新的协调(Reconciliation)引擎,完全重写了之前的 Stack Reconciler。

Fiber 通过 可中断的增量渲染、时间切片 和 任务优先级,将渲染工作拆分为多个小任务,允许浏览器在空闲时执行,从而实现流畅的用户体验。

Fiber 不仅仅是一个数据结构。Fiber 有三层含义:

2.1 作为一种数据结构

每个 Fiber 节点对应一个 React 元素(组件、DOM 节点等),它是一个 链表结构的 JavaScript 对象。典型的 Fiber 节点包含:

  • type:组件类型("div"MyComponent
  • key:用于列表 diff
  • child:指向第一个子 Fiber
  • sibling:指向下一个兄弟 Fiber
  • return:指向父 Fiber
  • alternate:双缓冲相关。指向另一棵树的对应节点(current 或 workInProgress)
  • stateNode:对应的真实 DOM 节点或组件实例
  • pendingProps / memoizedProps:新旧 props
  • memoizedState:上次渲染的状态
  • updateQueue:存放待处理的更新
  • flags(旧称 effectTag):标记这个节点需要执行什么操作(增、删、改)
  • lanes:优先级

这种链表结构使得 遍历过程可以被暂停和恢复。因为只需要保留当前节点的指针,下次从中断处继续即可。

2.2 作为一种执行单元

Fiber 也是一个 工作单元(Unit of Work)。在 React 内部,每次更新会被拆分成多个小任务,每个任务处理一个 Fiber 节点。任务可以按优先级插入队列,由调度器(Scheduler)统一管理。

2.3 作为一种架构理念

Fiber 架构重新设计了 React 的运行时模型,把原本单一的递归调用栈,变成了一个可以在多帧中分批执行的工作循环。


3. Fiber 解决了什么问题?—— 时间切片 + 优先级调度

Fiber 带来的核心能力是:可中断、可恢复、可优先级的异步渲染

  • 时间切片:React 将长时间渲染任务切分成多个小任务,每个任务执行时间控制在约 5ms(一帧的预算内),并在每小任务后主动让出主线程。这样浏览器就有机会响应用户输入、动画等。

    Fiber 的支持方式:

    • 渲染工作被拆分为每个 Fiber 节点一个工作单元。
    • 工作循环(workLoop)每次处理一个单元,之后检查是否超出时间预算。
    • 如果剩余时间不足(如 <1ms),则调用 yield 终止循环,通过调度器发起下一次继续。
    • 调度器利用 MessageChannel 或 requestIdleCallback 在帧空闲时继续执行
  • 优先级调度:不同的更新可以拥有不同的优先级。例如:

    • 离散事件(如用户点击、输入):最高优先级 ImmediatePriority
    • 连续事件(如滚动、动画):高优先级 UserBlockingPriority
    • 数据请求、过渡更新:普通优先级 NormalPriority
    • 预渲染、后台任务:低优先级 LowPriority
    • 闲置任务:IdlePriority
  • 并发模式(Concurrent Mode):React 可以在内存中同时准备多个版本的 UI,等合适的时候再提交到屏幕。这是 Suspense、useTransition、useDeferredValue 等特性的底层基础。

结合上方内容,一句话总结:Fiber是一个新的协调机制,解决了React15及之前的同步递归不可中断、缺乏优先级调度、无法在渲染过程中做额外工作等问题,通过可中断的增量渲染、时间切片 和 任务优先级,将渲染工作拆分为多个小任务,允许浏览器在空闲时执行,从而实现流畅的用户体验。


4. Fiber 的工作流程(核心架构与工作原理)

整个工作流程分为两大阶段:Render 阶段Commit 阶段

4.1 Render 阶段 —— 可中断的构建

这个阶段的目标:构建 Fiber 树(workInProgress tree),并找出哪些节点发生了变化(收集 Effect)。

  • 入口performSyncWorkOnRootperformConcurrentWorkOnRoot
  • 核心函数workLoopSync / workLoopConcurrent
  • 过程
    1. 从根 Fiber 开始,深度优先遍历(先 child 后 sibling)。

    2. 对每个 Fiber 节点调用 beginWork(递的过程):

      根据组件类型(FunctionComponent, ClassComponent, HostComponent 等)执行对应的 diff 和更新逻辑,生成新的子 Fiber。

    3. 当遍历到叶子节点(没有 child)时,调用 completeWork(归的过程):

      收集副作用(如 DOM 操作、生命周期调用),并向上冒泡。

      如果有兄弟节点,怎进入到兄弟节点的递阶段;如果不存在兄弟节点,那就会进入到父节点Fiber的归阶段

    4. 每处理完一个 Fiber 节点,检查是否超时shouldYield)。

      在并发模式下,如果超出时间片,则暂停循环,保存当前进度,把控制权交还给浏览器。下一帧恢复时,从中断的地方继续。

这个阶段构建的 workInProgress 树是一个双缓冲(double buffering)结构,与当前的 current 树隔离。所有的工作都在内存中进行,用户看到的内容不会改变


调和就放生在这个阶段。调和是 React 比较新旧虚拟 DOM 树(Fiber 树)并确定最小变更集的过程。

调和核心算法是 Diffing,主要规则如下:

① 同层比较: 只比较同一层级的节点,不跨层级移动。
② 不同类型的组件: 如果两个节点类型不同(如 div → p),React 会销毁旧子树(执行卸载生命周 期)并创建新子树,不会尝试对比子节点。
③ 相同类型的DOM元素:对比属性(className、style 等),只更新变化的属性。
④ 相同类型的组件: 组件实例保持不变,更新 props 并调用生命周期(如 componentWillReceiveProps), 递归对比子节点。
⑤ 列表 key 优化:对于子节点列表,使用 key 属性来识别哪些元素被移动、添加或删除,避免低 效的逐个比较。
调和在 Render 阶段执行,基于类型、key、层级等规则高效比较新旧 Fiber 树,标记出需要更新的 DOM 节点。

4.2 Commit 阶段 —— 同步且不可中断

一旦 Render 阶段完成,得到了一棵完整的 workInProgress 树,以及一条 Effect 链表(记录了哪些节点需要增、删、改,哪些 Ref 需要更新,哪些生命周期需要调用),就进入 Commit 阶段。

Commit 阶段的核心工作由 commitRoot 函数驱动,它分为三个子步骤

  • before mutationDOM 变更前

    • 调用 getSnapshotBeforeUpdate,读取 DOM 状态,让组件在 DOM 变更前获取旧值(如滚动位置)。执行清理函数。
  • mutation(DOM 变更)DOM 变更。直接操作真实 DOM(插入、删除、更新属性)。这个阶段是同步的,因为需要保证 DOM 变更的原子性。

    • 对于 Placement:调用 appendChild / insertBefore 添加节点。
    • 对于 Update:更新 DOM 属性(class、style、事件监听等)。
    • 对于 Deletion:调用 componentWillUnmountuseEffect 清理函数,然后移除节点。
    • 在此阶段,会调用上一次 useLayoutEffect 的清理函数(即清除之前的 effect,以避免状态不一致。)
  • layout(commit 后)DOM 变更后、浏览器绘制前

    • 调用 componentidMount / componentDidUpdate(类组件);
    • 同步调用所有的 useLayoutEffect 的回调函数(新 effect);
    • 调度 useEffect 的回调(放入任务队列,等待浏览器空闲执行);
    • 更新 refs 等。

Commit 结束后,workInProgress 树成为新的 current 树,等待下一次更新。浏览器会进行重绘(Paint),用户看到最终界面


🤔 为什么不能中断?

因为浏览器一旦开始绘制,中间状态的更改会导致 UI 不一致。所以所有 DOM 变更必须一次性同步完成,才能保证界面一致性和生命周期语义的正确性。

Commit 阶段必须是同步且不可中断,主要基于以下原因:

① 用户可见的一致性:Commit 阶段直接操作真实 DOM,如果在中间被打断,用户可能看到部分更新的界面(例如半个列表被插入),造成视觉不一致或状态损坏。
② 浏览器 API 不支持中断:一旦开始调用 appendChild、removeChild、setAttribute 等 DOM 变更方法,无法“回滚”或“暂停”。如果中断,后续恢复时无法确定当前 DOM 的状态。
③ 依赖同步执行的生命周期:componentDidMount、useLayoutEffect、getSnapshotBeforeUpdate等期望在 DOM 变更后立即执行,以进行布局测量或同步修正。异步化会使它们的行为难以预测。
④ 避免竞态条件:多个更新如果同时进入 Commit 阶段,需要保证最终 DOM 状态与最后一次更新一致。同步执行可以确保顺序性和原子性。

因此,React 的设计是:Render 阶段可中断,负责计算;Commit 阶段不可中断,负责应用。

🤔 在这里也经常被问到:“useEffect 和 useLayoutEffect 分别在哪个阶段执行?

用这个时序图就可以一目了然:

Render 阶段(计算副作用,但不应用)
    ↓
Commit 阶段开始
    ├── Before Mutation(调用 getSnapshotBeforeUpdate)
    ├── Mutation(执行 DOM 操作)
    │     └── 在此阶段,会调用上一次 useLayoutEffect 的清理函数(即清除之前的 effect)
    ├── Layout(同步调用所有 useLayoutEffect 的回调函数)
    └── Commit 阶段结束
    ↓
浏览器执行绘制(Paint),用户看到更新后的界面
    ↓
(之后空闲时)异步调用 useEffect 的回调

useLayoutEffect:在 Commit 阶段同步执行。 具体时机是 React 完成所有 DOM 变更后、浏览器执行绘制(Paint)之前。它可用于读取布局(如 getBoundingClientRect)或同步修改 DOM 以避免闪烁。因为它会阻塞浏览器绘制,过度使用可能导致性能问题。

useEffect:在 Commit 阶段之后异步调度。 React 将所有 useEffect 函数收集起来,在浏览器绘制(Paint)之后,在浏览器空闲时通过 Scheduler (或下一次 Paint 之后)执行。不会阻塞屏幕更新,适用于数据获取、订阅、日志等大多数副作用。


5. 协调器、渲染器与工作循环的协作

  • 协调器(Reconciler):负责 Fiber 的 Diff 与标记更新。它不关心宿主环境(DOM、Native、Canvas 等),只负责生成 Effect。也就是上面描述的 Render 阶段的工作。
  • 渲染器(Renderer):负责把 Effect 应用到具体宿主环境。例如 react-dom 负责操作 DOM,react-native 负责调用原生视图接口。也就是上面描述的 Commit 阶段。
  • 工作循环(Work Loop):即 workLoop 函数,它是整个 Fiber 架构的驱动器。它决定何时开始一个任务、何时暂停、何时恢复、如何调度下一个任务。在 React 内部,由 Scheduler(调度器) 配合实现,调度器独立于 React,提供了类似 requestIdleCallback 但更强大的能力(如优先级、延时任务)。

image.png

核心记忆点

  • 调度器:负责“什么时候做” —— 优先级 + 时间切片。
  • 协调器:负责“做什么改变” —— diff + 标记 effect。
  • 渲染器:负责“真正去做” —— 操作 DOM + 生命周期。

6. Lane 模型 —— 更精细的优先级

优先级工作方式:

  1. React 17+ 采用 lane 优先级模型(二进制位运算的优先级系统),每个 Fiber 节点及更新(update)都带有一个或多个 Lane(例如 SyncLane、InputContinuousLane、DefaultLane、IdleLane)
  2. 调度器维护一个任务队列,每次选择最高优先级的任务执行
  3. 当高优先级任务打断低优先级任务时,低优先级任务会被挂起,其工作进度保留(workInProgress 树不丢弃),高优先级完成后,再基于原有进度重新恢复低优先级任务(可能丢弃部分已做工作)。
  4. 通过饥饿避免机制:长时间未执行的低优先级任务会被“提升”优先级,防止一直被饿死(确保低优先级更新的任务不会无限期被挂起。)

7. 总结:Fiber 带来的不仅仅是性能

维度React 15 (Stack)React 16+ (Fiber)
遍历方式递归,不可中断循环链表,可中断/恢复
任务拆分整个树为一个大任务每个节点为一个工作单元
时间片约 5ms
优先级Lane 模型,细粒度优先级
并发渲染不支持支持(Concurrent Mode)
适用场景中小型应用大型、交互复杂的应用

Fiber 架构并没有让 React 的核心编程模型(JSX、组件、state、props)发生任何改变,它是一次彻底的底层重构,让 React 在保持“声明式 UI”优点的同时,获得了接近“命令式性能优化”的能力。