说说对 Fiber 架构的理解?解决了什么问题?
一、是什么
Fiber 是 React 16 引入的全新协调引擎(Reconciler),它重新定义了 React 内部的工作方式。Fiber 既是一种数据结构(描述组件树中每个节点的信息),也是一种工作调度机制(实现可中断的增量渲染)。
在 Fiber 之前,React 使用 Stack Reconciler(栈协调器),它的更新过程是同步且不可中断的。Fiber 的引入让 React 能够将渲染工作拆分为多个小单元,在每个单元完成后检查是否需要让出主线程,从而避免长时间阻塞导致的界面卡顿。
二、Stack Reconciler 的问题
同步递归不可中断
旧的 Stack Reconciler 采用递归方式遍历组件树。一旦开始协调就必须同步完成整棵树的 diff 和更新,中途无法暂停:
更新开始 ─────────────────────────────────> 更新完成
|←── 这段时间主线程被完全占用 ──→|
[React 协调 100ms] [浏览器渲染] [用户输入响应延迟]
当组件树很大或更新频繁时,这段同步执行时间可能超过 16ms(60fps 下每帧的预算),导致:
- 动画掉帧:浏览器无法及时执行
requestAnimationFrame
- 输入延迟:用户的键盘输入、点击事件无法及时响应
- 页面卡顿:长时间占用主线程,浏览器无法进行布局和绘制
无优先级概念
Stack Reconciler 中所有更新的优先级相同。一个低优先级的大列表更新和一个高优先级的用户输入响应会被同等对待,先进先出:
用户正在输入 → 触发输入更新(高优先级)
同时后台数据更新 → 触发列表渲染(低优先级)
Stack Reconciler:先处理先到达的,可能先渲染列表再响应输入
Fiber:可以中断列表渲染,优先响应用户输入
三、Fiber 节点数据结构
每个 React 元素对应一个 Fiber 节点,它是一个包含丰富信息的 JavaScript 对象:
interface FiberNode {
// === 静态结构信息 ===
tag: number; // 组件类型标识(FunctionComponent=0, ClassComponent=1, HostComponent=5...)
type: any; // 对应的 React 元素类型(函数、类、或 'div' 等字符串)
key: string | null; // 用于 diff 的 key
stateNode: any; // 真实 DOM 节点(HostComponent)或类组件实例
// === 树结构指针 ===
child: FiberNode | null; // 第一个子节点
sibling: FiberNode | null; // 下一个兄弟节点
return: FiberNode | null; // 父节点
// === 工作单元数据 ===
pendingProps: any; // 本次更新待处理的 props
memoizedProps: any; // 上次渲染完成的 props
memoizedState: any; // 上次渲染完成的 state(Hooks 链表)
updateQueue: any; // 更新队列
// === 副作用 ===
flags: number; // 副作用标记(Placement, Update, Deletion...)
subtreeFlags: number; // 子树的副作用标记(冒泡收集)
// === 调度相关 ===
lanes: number; // 本节点的优先级车道
childLanes: number; // 子树中的优先级车道
// === 双缓冲 ===
alternate: FiberNode | null; // 指向另一棵树中的对应节点
}
链表树结构
Fiber 使用 child/sibling/return 三个指针将树结构表示为链表,使得遍历可以用循环(而非递归)实现,从而支持中断和恢复:
App (return: null)
|
| child
↓
Header (return: App) ──sibling──→ Main (return: App) ──sibling──→ Footer
| |
| child | child
↓ ↓
Logo ──sibling──→ Nav Content ──sibling──→ Sidebar
遍历顺序:先深入 child,没有 child 则访问 sibling,没有 sibling 则回到 return 找 sibling,以此循环直到回到根节点。
四、工作循环(Work Loop)
Fiber 的核心调度逻辑是 Work Loop,它将整棵树的协调工作拆分为一个个 Fiber 节点的处理单元:
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const next = beginWork(unitOfWork); // 处理当前节点,返回 child
if (next !== null) {
workInProgress = next; // 有 child,继续深入
} else {
completeUnitOfWork(unitOfWork); // 无 child,完成当前节点
}
}
关键点在于 shouldYield()——每处理完一个 Fiber 节点后,都检查是否需要让出主线程。如果浏览器需要处理更高优先级的任务(如用户输入、动画帧),React 会暂停当前工作,交出控制权。
Scheduler(调度器)
React 实现了自己的调度器(Scheduler),不再依赖 requestIdleCallback(兼容性差且触发频率低),而是基于 MessageChannel 实现的时间切片:
一帧(约 16ms)
├── 用户输入处理
├── JavaScript 执行
│ ├── React 工作切片 1(~5ms)
│ ├── 让出主线程 → 浏览器布局/绘制
│ ├── React 工作切片 2(~5ms)
│ └── 让出主线程 → 浏览器布局/绘制
├── 布局(Layout)
└── 绘制(Paint)
五、优先级与 Lanes 模型
React 18 使用 Lanes(车道)模型管理更新优先级,每个更新被分配到特定的 lane:
高优先级 ─────────────────────── 低优先级
SyncLane | InputContinuousLane | DefaultLane | TransitionLane | IdleLane
同步 | 连续输入 | 默认 | 过渡 | 空闲
(点击) | (滚动/拖拽) | (网络请求) | (useTransition) | (预渲染)
不同优先级的更新可以并行存在。高优先级更新可以打断低优先级的渲染工作:
function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value); // SyncLane:高优先级,立即更新
startTransition(() => {
setResults(search(e.target.value)); // TransitionLane:低优先级,可中断
});
};
return (
<div>
<input value={query} onChange={handleInput} />
{isPending ? <Spinner /> : <ResultList results={results} />}
</div>
);
}
六、双缓冲(Double Buffering)
React 维护两棵 Fiber 树:
- current 树:当前屏幕上显示的 UI 对应的 Fiber 树
- workInProgress 树:正在构建中的新 Fiber 树
current 树(屏幕显示) workInProgress 树(后台构建)
App ←── alternate ──→ App'
/ \ / \
Header Main Header' Main'
| | | |
Logo Content Logo' Content'
← 两棵树的对应节点通过 alternate 指针互相引用 →
工作流程
- 开始更新:基于 current 树创建 workInProgress 树(尽可能复用现有 Fiber 节点)
- 协调过程:在 workInProgress 树上进行 diff 和计算
- 提交更新:workInProgress 树完成后,在 commit 阶段一次性替换为 current 树
- 角色互换:旧的 current 树成为下次更新的 workInProgress 基础
这种机制保证了用户看到的 UI 始终是完整一致的,不会看到渲染了一半的中间状态。
七、两大阶段
Render 阶段(可中断)
遍历 Fiber 树,执行组件函数/render 方法,比较前后差异,标记需要的 DOM 操作。这个阶段不产生可见的副作用,因此可以安全地中断和重新开始。
每个 Fiber 节点经历两个步骤:
beginWork(向下递)
├── 根据 tag 处理不同类型的组件
├── 调用函数组件 / render 方法
├── 进行 diff 比较
└── 返回子 Fiber 节点
↓
completeWork(向上归)
├── 创建或更新 DOM 节点
├── 收集子树的 flags(冒泡)
└── 构建 Effect List
Commit 阶段(不可中断)
将 render 阶段标记的变更应用到真实 DOM。这个阶段必须同步完成:
commit 阶段
├── Before Mutation(DOM 操作前)
│ ├── 调用 getSnapshotBeforeUpdate
│ └── 调度 useEffect(异步)
│
├── Mutation(执行 DOM 操作)
│ ├── 插入(Placement)
│ ├── 更新(Update)
│ └── 删除(Deletion)
│
└── Layout(DOM 操作后)
├── 调用 componentDidMount / componentDidUpdate
├── 调用 useLayoutEffect
└── current 树切换(workInProgress → current)
八、Fiber 带来的能力
九、总结
Fiber 架构解决了 Stack Reconciler 的核心问题——同步递归阻塞主线程。它通过三个关键设计实现了可中断的增量渲染:
- 数据结构重构:用链表(child/sibling/return)替代递归调用栈,使遍历可以暂停和恢复
- 工作循环:每个 Fiber 节点作为一个工作单元,处理完后检查是否需要让出控制权
- 双缓冲机制:current 和 workInProgress 两棵树交替使用,保证用户看到的 UI 始终一致
Fiber 是 React 18 并发特性(Concurrent Features)的底层基础,它让 React 从"同步阻塞式"框架进化为"可调度、可中断、有优先级"的现代 UI 运行时。