一、为什么需要Fiber架构
React 15的架构存在一个根本性缺陷:更新过程是"同步递归"的,一旦开始就无法中断。当组件树层级深、更新量大时,这个递归过程会独占主线程,导致动画卡顿、响应延迟。Fiber架构的本质,是将这个同步过程"异步化"、"可中断化"。
1.1 Stack Reconciler vs Fiber Reconciler
// React 15 Stack Reconciler(同步递归)
//
// function reconcile(children) {
// children.forEach(child => {
// const instance = createInstance(child); // 同步创建DOM
// reconcile(child.children); // 递归,无法中断
// appendAllChildren(instance); // 挂载DOM
// });
// }
// 问题:10万个组件的更新需要一次性完成
// ↓
// V8主线程被占用 ~16ms(1帧)
// 动画60fps → 每帧最多16.67ms
// 更新占用 > 16ms → 掉帧 → 卡顿
// React Fiber(可中断的增量更新)
//
// 工作单元(Fiber节点):
// type Fiber = {
// // ① 链表指针(替代递归栈)
// child: Fiber | null, // 第一个子节点
// sibling: Fiber | null, // 下一个兄弟节点
// return: Fiber | null, // 父节点
//
// // ② 工作类型
// tag: WorkTag, // 组件类型
// pendingProps: any, // 新props
// memoizedProps: any, // 旧props
//
// // ③ 状态
// memoizedState: any, // 已提交的state
// effectTag: SideEffectTag, // 本次更新的副作用标记
//
// // ④ 调度
// expirationTime: ExpirationTime, // 优先级
// childExpirationTime: ExpirationTime,
// };
//
// // 可中断的关键:每次只处理一个Fiber节点
// function workLoop() {
// while (nextUnitOfWork !== null) {
// nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
// }
// }
// requestIdleCallback(workLoop, { timeRemaining: 5 }); // 空闲时执行
// ↑
// JS主线程空闲时继续执行,否则让出给浏览器调度
二、Fiber树的构建与双缓冲技术
2.1 双缓冲(Double Buffering)
// React Fiber使用"双缓冲"技术管理两棵Fiber树
//
// current Fiber树 workInProgress Fiber树
// ┌──────────────┐ ┌──────────────┐
// │ 已渲染的 │ │ 正在构建的 │
// │ 真实DOM对应 │ ← │ 新更新 │
// └──────────────┘ └──────────────┘
//
// current.alternate === workInProgress
// workInProgress.alternate === current
// 开始更新时:从current克隆出workInProgress
function createWorkInProgress(current, pendingProps) {
let workInProgress = current.alternate;
if (workInProgress === null) {
// 首次构建:clone一个Fiber节点
workInProgress = {
...current,
alternate: current, // 回指current
// 重置工作相关字段
pendingProps: pendingProps,
memoizedProps: null,
memoizedState: null,
effectTag: null,
child: null,
};
} else {
// 复用:重置状态,保留已有引用
workInProgress.pendingProps = pendingProps;
workInProgress.effectTag = null;
workInProgress.child = null;
}
return workInProgress;
}
// 调度流程:
// setState()
// ↓
// enqueueUpdate() ← 将update加入updateQueue
// ↓
// scheduleWork() ← 请求调度
// ↓
// requestWork() ← 决定是否立即reconcile
// ↓
// performWork() ← 遍历Fiber树,执行更新
// ↓
// completeRoot() ← 提交更新,交换双缓冲
// ↓
// current = workInProgress(交换引用)
2.2 三种提交阶段(Commit Phases)
// React更新分两个阶段:
// ① Render阶段:可中断(计算差异,构建workInProgress树)
// ② Commit阶段:同步执行(DOM操作、副作用执行)
// Commit阶段不允许中断,原因:
// DOM的多次读取/写入操作必须严格按顺序执行
// 例如:读取offsetHeight → 写入height → 读取offsetHeight
// 如果中间插入其他操作,会产生布局抖动
// Commit阶段又分三个子阶段:
// ┌──────────────────────────────────────────────┐
// │ Phase 1: Before Mutation(DOM操作前) │
// │ 执行:getSnapshotBeforeUpdate │
// │ ⚠️ 此阶段不能执行副作用 │
// └──────────────────────────────────────────────┘
// ↓
// ┌──────────────────────────────────────────────┐
// │ Phase 2: Mutation(DOM操作,同步) │
// │ 执行:所有ref绑定 │
// │ 类组件componentDidMount/Update │
// │ 函数组件的useEffect同步执行 │
// │ DOM的appendChild/removeChild等 │
// └──────────────────────────────────────────────┘
// ↓
// ┌──────────────────────────────────────────────┐
// │ Phase 3: Layout(布局后,DOM已更新) │
// │ 执行:useLayoutEffect的同步回调 │
// │ ref的DOM操作(.focus()/.scroll()) │
// └──────────────────────────────────────────────┘
// ⚠️ 为什么useEffect在mutation阶段之后执行?
// 因为useEffect是异步的,不阻塞DOM更新
// 浏览器可以在DOM更新后先显示,再执行副作用
三、优先级调度机制
3.1 优先级层级
// React Fiber的5级优先级(从高到低)
//
// SyncLane ← 最高:setState同步调用、DOM事件
// InputContinuousLane ← 连续输入:scroll、drag事件
// DefaultLane ← 普通:fetch结果、transition
// TransitionLane ← 过渡:useTransition标记的更新
// IdleLane ← 最低:预渲染、低优先级工作
// Lane模型(位运算实现)
// 用一个31位整数表示优先级
// 每个Lane对应一个bit位
//
// 0b0000000000000000000000000000001 ← SyncLane (1)
// 0b0000000000000000000000000000010 ← InputContinuousLane (2)
// 0b0000000000000000000000000000100 ← DefaultLane (4)
// 0b0000000000000000000000000001000 ← TransitionLane (8)
//
// 运算优势:
// - 判断是否有更高优先级任务:lanes & ~higherPriorityLanes
// - 合并多个Lane:lanes | newLane
// - 清除某个Lane:lanes & ~targetLane
// 优先级决定expirationTime
function computeExpirationTime(currentTime, priorityLevel) {
switch (priorityLevel) {
case SyncLane: return currentTime; // 立即(sync)
case InputContinuousLane: return currentTime - 3;
case DefaultLane: return currentTime - 5;
case TransitionLane: return currentTime - 10;
case IdleLane: return currentTime + 100;
}
}
// 过期判断:
// currentTime >= expirationTime → 必须立即执行(sync)
// currentTime < expirationTime → 可以被低优先级抢占
3.2 useTransition的实现原理
// useTransition让某些更新可被中断
import { useTransition } from 'react';
function SearchResults() {
const [isPending, startTransition] = useTransition();
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
function handleSearch(e) {
const value = e.target.value;
setQuery(value);
// startTransition内的更新优先级降低
// 即使渲染慢,也不会阻塞用户输入
startTransition(() => {
setResults(searchInLargeDataset(value));
});
}
return (
<div>
<input value={query} onChange={handleSearch} />
{isPending ? <Spinner /> : <Results data={results} />}
</div>
);
}
// 内部实现:
// startTransition(callback) {
// callback(); // callback内所有setState → TransitionLane
// }
// 效果:
// 输入框更新 → SyncLane(立即,高优先级)→ 输入立即响应
// 结果列表更新 → TransitionLane(可中断)→ 可以被输入抢占
四、Diff算法的精准分析
4.1 三条核心规则
// React Diff的三条规则(决定了调和算法复杂度)
//
// 规则①:不同类型的元素产生不同的树
// <div> → <span> → 卸载div,替换为span子树
// 规则②:通过key优化同层级列表的移动
// 规则③:同一类型的元素只对比属性
// 规则①:元素类型变化 → 完整替换
// <div key="a">A</div>
// ↓
计划变成 → <section key="a">B</section>
// 实际:卸载整个div树(A被销毁),挂载新section树(B被创建)
// 规则③:同类型元素 → 只更新变化的属性
// <div className="old" style={{color:"red"}}>
// ↓ → 只更新className和style,不重新创建DOM节点
// <div className="new" style={{color:"blue"}}>
// 规则②:列表diff的核心 —— key的作用
//
// 无key(错误):
// [A, B, C] → [A, B, C](删除中间的B)
// 操作: 卸载C → 卸载A → 创建C → 创建A ← O(N²)
// 有key:
// [A, B, C] → [A, C](删除中间的B,key="A","B","C")
// 操作: 只卸载B ← O(N)
// ⚠️ 错误使用key导致性能倒退
// ❌ 用index作key(在列表重排时)
// items.map((item, index) => <Item key={index} />)
// 数组[1,2,3]→[3,2,1]时:所有Item都被重建
// ✅ 用稳定ID作key
// items.map(item => <Item key={item.id} />)
// 数组[1,2,3]→[3,2,1]时:复用已有节点,只需移动
五、Concurrent Mode的调度本质
// Concurrent Mode的并发调度器(基于Fiber的Scheduler)
//
// 调度器维护一个"小顶堆"(Min Heap)存储待执行任务:
// 优先级最高的任务在堆顶
//
// [Task: exp=5] ← 当前最高优先级
// / \
// [exp=8] [exp=12]
//
// requestHostCallback 调度循环:
// while (workInProgress !== null) {
// if (getCurrentTime() >= deadline) {
// // 时间片用完,主动让出主线程
// requestYield(); // → requestIdleCallback在下帧继续
// return; // 中断!其他任务可以执行
// }
// workInProgress = performUnitOfWork(workInProgress);
// }
//
// 任务可抢占的时机:
// 1. 子树遍历完一个节点
// 2. 遇到SuspenseBoundary(挂起)
// 3. 时间片耗尽
// Suspense的调度逻辑
<Suspense fallback={<Spinner />}>
<HeavyComponent /> ← 需要异步加载数据
</Suspense>
// HeavyComponent内部:
// if (!data) {
// throw promise; // 抛出Promise → Suspense捕获 → 显示fallback
// }
// ⚠️ 抛出Promise后,整个Fiber子树的工作被挂起
// Suspense外的更新不受影响(不受阻塞)