Fiber架构设计思想
一句话概括
React Fiber 是一套基于链表结构的可中断协调引擎,它将递归渲染拆解为可暂停、可优先级的异步工作单元,使 React 能在长渲染任务中主动让出主线程——这是 React 15 到 React 18 最根本的架构升级。
核心知识点
1. 递归 → 链表:为何 Fiber
React 15 的 Stack Reconciler 使用递归调用栈做 Diff——递归不可中断。一棵 5000 节点的树,一次渲染占用主线程 100ms+,用户点击、滚动全被堵住。
Fiber 的解法:把递归调用栈”扁平化”为显式的链表结构。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// React 15:递归调用(不可中断)
function reconcile(parentDOM, element) {
const dom = createDOM(element);
parentDOM.appendChild(dom);
element.children.forEach(c => reconcile(dom, c)); // 一直深入到底
}
// React 16+:Fiber 链表遍历(可中断)
function workLoop(deadline) {
while (nextUnitOfWork && !deadline.timeRemaining() < 1) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork); // 处理一个节点后检查时间
}
if (nextUnitOfWork) requestIdleCallback(workLoop); // 剩余工作下一帧继续
}
2. Fiber 节点结构:child → sibling → return
一棵 div 下有两个子元素的 Fiber 树:
1
2
3
4
App (FunctionComponent)
└─ child → div (HostComponent)
├─ child → h1 → sibling → p
└─ return → App
通过 child 进入子树,通过 sibling 横向跳转,通过 return 回溯父节点。这三个指针让遍历变成了非递归的循环——可以在任意节点暂停和恢复。
关键属性:
type/key:标识节点身份memoizedState:Hook 链表 / 旧状态alternate:指向双缓冲中对应的”旧树节点”flags/subtreeFlags:副作用标记(需要增/删/改)lanes/childLanes:优先级位掩码
3. 遍历过程:beginWork 向下 + completeWork 向上
1
2
3
beginWork(App) → beginWork(div) → beginWork(h1)
→ h1无子节点 → completeWork(h1) → 有sibling(p) → beginWork(p)
→ completeWork(p) → completeWork(div) → completeWork(App) → 结束
- beginWork:判断节点是否需要更新,是则执行 diff 生成新子节点,否则 bailout(跳过整个子树)
- completeWork:创建/更新 DOM,把子节点 DOM 归约到父节点,构建 effectList
bailout 是 Fiber 最重要的性能优化——如果 props 没变、context 没变、子树也没更新,beginWork 直接返回 null 跳过,不深入。
4. Lane 优先级模型(32 位位掩码)
React 18 用 32 位二进制位表示优先级:
1
2
3
4
5
const SyncLane = 0b0001; // 位 0:用户点击、输入,立即同步执行
const InputContinuousLane = 0b0010; // 位 1:滚动、拖拽
const DefaultLane = 0b0100; // 位 2:普通 setState
const TransitionLane = 0b1000_0000; // 位 7+:startTransition
const IdleLane = 0b1000_0000_0000; // 低位:空闲时
位运算操作都 O(1):
1
2
3
lanesA | lanesB // 合并优先级
lanesA & lanesB // 取交集
lanes & -lanes // 提取最低位(最高优先级!)
useTransition 包裹的更新走 TransitionLane,用户点击走 SyncLane,点击总是优先打断 transition 渲染。饥饿保护:低优先级 Lane 等待超时后自动提升为同步执行。
5. 时间切片:5ms 预算
Chrome 60fps → 每帧 16.6ms。React 给 JS 执行留 5ms,剩下约 11ms 给浏览器布局+绘制。
React Scheduler 用 MessageChannel(优先级高于 setTimeout 的宏任务)模拟时间切片:
1
2
3
4
5
6
7
function performWorkUntilDeadline() {
const deadline = performance.now() + 5; // 5ms 切片
while (currentTask && performance.now() < deadline && !shouldYield()) {
// 执行一个任务切片
}
if (currentTask) port.postMessage(null); // 安排下一帧继续
}
requestIdleCallback 触发频率太低(20-50fps)且兼容性差,React 不用它。
「其实你每天都在用」
React.memo为什么能优化:它保证oldProps === newProps(引用不变),配合 Fiber 的 bailout 机制可以跳过大片子树的渲染。输入框不卡顿的秘密:
useDeferredValue让输入更新走 SyncLane(立即响应输入),过滤列表走 TransitionLane(分片渲染,可被下一次输入打断)。Tab 切换流畅:
startTransition(() => setTab('analytics'))让 tab 按钮高亮立即生效,内容渲染后台分片执行,快速切换多个 tab 不会卡。Suspense 的底层支持:Fiber 的双缓冲让挂起的渲染可以暂存 workInProgress 树,数据到达后续传,不影响 current 树显示 fallback。
useEffect 在渲染后执行:effect 回调在 commit 阶段收集,浏览器绘制后
MessageChannel异步执行,不阻塞 DOM 更新。
常见误解 (FAQ)
❌ 误区 1:”Fiber 优化了 Diff 算法,让两棵树比较更快”
Diff 算法本身在 React 15 就是 O(n) 的,已经足够快。Fiber 解决的是调度问题——不是比更快,是比到一半能停下来去处理用户点击。
❌ 误区 2:”双缓冲 = 两个 DOM 树”
双缓冲的是 Fiber 树(内存中的 JS 对象),不是两棵 DOM 树。用户在屏幕上永远看到的是 current 树对应的那棵 DOM 树,workInProgress 是”草稿”,提交后才切换。
❌ 误区 3:”组件树越大越需要 Fiber”
不是所有场景都受益。组件少于 1000 个的页面,时间切片带来的额外调度开销可能比本身的收益大。Fiber 解决的是极端场景——数千节点的列表渲染、复杂表单的全量校验等。
❌ 误区 4:”React 18 所有更新都是异步的”
SyncLane 的更新(点击、输入、flushSync 包裹)仍然是同步的,不经过 Scheduler。只有 TransitionLane、DefaultLane 等才走时间切片。
一句话总结
Fiber 把”不可中断的递归”变成了”可暂停、可排序、可丢弃的链表遍历”——它不是优化了 React,而是重新定义了 React 能做什么。