FumadocsZDecode
React

React Fiber 架构

Fiber 是 React 16 引入的新协调(Reconciliation)架构,本质上是「可中断、可恢复、可优先级调度」的渲染机制。它把旧版「一口气递归到底」的更新,改成「能做多少做多少,时间不够就停」。


一、为什么需要 Fiber

React 16 之前用的是 Stack Reconciler,更新流程是同步递归:

setState
  -> 递归 diff 整棵树
      -> 一次性同步执行到底
          -> commit DOM

问题在于:调用栈递归一旦开始就无法暂停。当页面有 5000 个组件需要更新时,React 必须一次性算完。而 JS 是单线程,渲染期间用户的点击、输入、动画、滚动全部无法响应——这就是主线程阻塞

Fiber 的核心思想:把「递归不可中断任务」改成「可拆分的小任务」。

旧:一口气干完
新:干一点 → 暂停 → 浏览器先响应用户 → 有空再继续

二、Fiber 的数据结构

Fiber 不再用递归调用栈,而是用链表节点记录遍历进度。每个组件对应一个 FiberNode:

interface FiberNode {
  type: any         // 组件类型
  stateNode: any    // 对应的真实 DOM 或组件实例
  child: FiberNode | null    // 第一个子节点
  sibling: FiberNode | null  // 下一个兄弟节点
  return: FiberNode | null   // 父节点
  alternate: FiberNode | null // 指向双缓存中的另一棵树
}
字段作用
child第一个子节点
sibling下一个兄弟节点
return父节点
alternate双缓存树的另一棵

为什么用链表而不用递归? 递归无法暂停,而链表遍历可以让 React 自己控制「下一个执行谁」「什么时候暂停」,这是 Fiber 能中断的核心。


三、逐行拆解 workLoop

workLoop 是 Fiber「可中断循环」的最小骨架(教学版,类似 Dan Abramov 的 Didact):

function workLoop(deadline) {
  while (nextUnit && deadline.timeRemaining() > 0) {
    nextUnit = performUnitOfWork(nextUnit)
  }

  if (nextUnit) requestIdleCallback(workLoop)
  else commitRoot()
}

循环条件nextUnit(还有没处理完的节点)和 deadline.timeRemaining() > 0(本帧还有空闲时间)必须同时满足,任一不满足就跳出。这就是「时间切片」的开关——靠时间判断决定要不要让出主线程。

进度保存nextUnit模块级变量而非局部变量,它记录了遍历进度。即使循环被打断退出,进度也保存在它身上,下次进来能接着做——这是断点恢复的关键。

两种退出的区分

退出原因nextUnit 状态后续动作
时间用光(被打断)还有值requestIdleCallback 重新排队,下一帧继续
任务全部完成为空进入 commitRoot,更新 DOM

真实 React 并未直接用 requestIdleCallback(触发频率不稳、兼容性差),而是用自己的 Scheduler 包,底层基于 MessageChannel 模拟出「空闲回调 + 优先级」的能力。原理一致。


四、两个阶段:Render 与 Commit

Fiber 把更新分成两个阶段:

Render 阶段(协调阶段):构建新 Fiber 树、diff 新旧节点、收集副作用。可以暂停、恢复、终止

// 核心循环:一次只处理一个 Fiber 节点
while (workInProgress && !shouldYield()) {
  workInProgress = performUnitOfWork(workInProgress)
}

shouldYield() 判断「是否该把控制权还给浏览器」,时间快到时返回 true,React 就暂停。

Commit 阶段:真正修改 DOM(appendChildremoveChild、更新属性、执行 useEffect)。不可中断,因为 DOM 必须保证一致性,否则页面会出现「更新一半」的状态。


五、时间切片(Time Slicing)

时间切片就是把长任务切成很多小片段执行:

旧:一个任务执行 200ms       → 用户感觉卡顿
新:5ms React → 5ms 浏览器
    → 5ms React → 5ms 浏览器  → 页面不卡,动画流畅

为什么留时间给浏览器? 浏览器一帧约 16.6ms(60fps),React 每做一小段就让出主线程,保证浏览器有时间渲染动画、响应输入。


六、双缓存树(Double Buffering)

Fiber 同时维护两棵树,通过 alternate 字段互相指向:

current tree         (当前显示的树,不动)
workInProgress tree  (正在构建的新树)

更新时旧树不动,在内存中构建新树;全部完成后直接切换指针

// 一次性切换,避免用户看到中间状态
root.current = workInProgress

这样保证用户要么看到旧画面、要么看到新画面,不会看到构建到一半的树。


七、Fiber 解决了什么

问题旧 ReactFiber
页面卡顿长时间阻塞主线程可中断渲染
动画掉帧无法插队优先处理高优先级任务(点击、输入)
并发特性不支持支撑 Concurrent、Suspense、transition、selective hydration

React 18 的并发能力(Concurrent Rendering、Suspense、useTransition)全部依赖 Fiber 架构。


核心三句话:Fiber 把每个组件转成 FiberNode,用链表遍历替代递归,从而能暂停和恢复。Render 阶段可中断、负责计算和 diff;Commit 阶段不可中断、负责真实 DOM 更新。时间切片让 React 每帧只占用一小段时间,把主线程及时还给浏览器,避免卡顿。

On this page