React 触发setState后的完整执行流程分析
Lane是 React 18 引入的优先级模型,使用31 位二进制数表示不同的优先级。// 过渡优先级(共 15 个)// 其他机制作用实现方式Lane 标记标记哪些 Fiber 需要更新中调用childLanes 传播标记更新路径向上标记所有父 Fiber 的childLanesbailout 优化跳过不需要更新的子树Props 对比判断组件是否需要重新渲染oldProps!↓【标记阶段】├─
今天我们来看看,React触发了setState后,执行逻辑和首次执行有什么不一样。下面会以一个示例为切入口来讲解整个过程,重点会分析setState触发时的优先级机制以及React做了的一些优化点。
目录
- 示例项目场景
- setState 调用瞬间发生了什么
- Update 对象的创建与入队
- 调度更新的触发
- 优先级计算与Lane模型
- Render阶段的完整流程
- Render阶段性能优化:Lane标记与Bailout机制
- Commit阶段的完整流程
- 批量更新机制
- 完整时序图
1. 示例项目场景
1.1 项目背景
我们以一个电商商品列表页为例:
// ProductList.jsx - 商品列表组件
import React, { useState, useEffect } from 'react';
function ProductList() {
const [products, setProducts] = useState([]);
const [filterCategory, setFilterCategory] = useState('all');
const [sortBy, setSortBy] = useState('price');
const [loading, setLoading] = useState(false);
useEffect(() => {
fetchProducts();
}, [filterCategory, sortBy]);
const fetchProducts = async () => {
setLoading(true);
const data = await api.getProducts({ category: filterCategory, sort: sortBy });
setProducts(data);
setLoading(false);
};
const handleCategoryChange = (category) => {
// 重点:这里调用了 setState
setFilterCategory(category); // ← 我们要分析这个调用
};
return (
<div>
<CategoryFilter
selected={filterCategory}
onChange={handleCategoryChange}
/>
<SortOptions selected={sortBy} onChange={setSortBy} />
{loading ? (
<LoadingSpinner />
) : (
<ProductGrid products={products} />
)}
</div>
);
}
1.2 组件树结构
App
├─ Header
├─ ProductList (我们关注的组件)
│ ├─ CategoryFilter
│ │ └─ FilterButton (点击这里触发 setState)
│ ├─ SortOptions
│ └─ ProductGrid
│ ├─ ProductCard
│ ├─ ProductCard
│ └─ ProductCard
└─ Footer
1.3 触发场景
用户操作:点击"电子产品"分类按钮
// FilterButton.jsx
function FilterButton({ category, isSelected, onClick }) {
return (
<button
className={isSelected ? 'active' : ''}
onClick={() => onClick(category)} // ← 用户点击
>
{category}
</button>
);
}
// 用户点击 → onClick('electronics') → handleCategoryChange('electronics')
// → setFilterCategory('electronics') ← 从这里开始分析
2. setState 调用瞬间发生了什么
2.1 代码执行路径
// 用户点击按钮
onClick('electronics')
↓
// 调用父组件的回调
handleCategoryChange('electronics')
↓
// 调用 setState
setFilterCategory('electronics')
setFilterCategory 是什么?
const [filterCategory, setFilterCategory] = useState('all');
// ↑
// 这是 dispatchSetState 函数(通过 bind 绑定了参数)
2.2 dispatchSetState 函数分析
文件位置:packages/react-reconciler/src/ReactFiberHooks.js:3599
// setFilterCategory('electronics') 实际调用:
function dispatchSetState<S, A>(
fiber: Fiber, // ProductList 组件的 Fiber 节点(已绑定)
queue: UpdateQueue<S, A>, // filterCategory 的更新队列(已绑定)
action: A, // 'electronics'(用户传入)
): void {
// ========== 步骤1:参数验证 ==========
if (__DEV__) {
// 检查是否传入了第二个参数(旧版API)
const args = arguments;
if (typeof args[3] === 'function') {
console.error(
"State updates don't support the second callback argument. " +
"Use useEffect() instead."
);
}
}
// ========== 步骤2:计算更新优先级 ==========
const lane = requestUpdateLane(fiber);
// lane 的值可能是:
// - SyncLane (同步优先级,如 flushSync 或 Legacy 模式)
// - DefaultLane (默认优先级,如用户交互)
// - TransitionLane (过渡优先级,如 startTransition)
// ========== 步骤3:调用内部实现 ==========
const didScheduleUpdate = dispatchSetStateInternal(
fiber,
queue,
action, // 'electronics'
lane,
);
// ========== 步骤4:性能追踪(DEV) ==========
if (didScheduleUpdate) {
startUpdateTimerByLane(lane, 'setState()', fiber);
}
// ========== 步骤5:DevTools 标记 ==========
markUpdateInDevTools(fiber, lane, action);
}
此时的关键变量:
fiber = {
tag: FunctionComponent, // 类型:函数组件
type: ProductList, // 组件函数
stateNode: null, // 函数组件没有实例
memoizedState: Hook链表, // 指向第一个 Hook
updateQueue: null, // 函数组件的 updateQueue 在 Hook 上
// ...
}
queue = {
pending: null, // 待处理的更新(环形链表)
lanes: NoLanes, // 当前队列的优先级
dispatch: setFilterCategory, // dispatch 函数(就是自己)
lastRenderedReducer: basicStateReducer, // 状态计算函数
lastRenderedState: 'all', // 上次渲染的值
}
action = 'electronics' // 新的状态值
lane = DefaultLane // 假设是用户交互,使用默认优先级
2.3 requestUpdateLane:计算优先级
文件位置:packages/react-reconciler/src/ReactFiberWorkLoop.js
export function requestUpdateLane(fiber: Fiber): Lane {
// 步骤1:检查是否在 transition 中
const transition = requestCurrentTransition();
if (transition !== null) {
// 在 startTransition 中调用,使用过渡优先级
return requestTransitionLane(transition);
}
// 步骤2:获取当前的更新优先级
const updateLane = getCurrentUpdatePriority();
if (updateLane !== NoLane) {
return updateLane;
}
// 步骤3:根据事件优先级计算
const eventLane = getCurrentEventPriority();
return eventLane;
// 对于我们的案例:
// - 用户点击事件 → DiscreteEventPriority → SyncLane
// - 但在并发模式下可能是 DefaultLane
}
优先级映射表:
| 事件类型 | 事件优先级 | Lane | 示例 |
|---|---|---|---|
| 用户交互 | DiscreteEventPriority | SyncLane / InputContinuousLane | 点击、输入 |
| 连续事件 | ContinuousEventPriority | DefaultLane | 滚动、鼠标移动 |
| 默认 | DefaultEventPriority | DefaultLane | setTimeout |
| 空闲 | IdleEventPriority | IdleLane | requestIdleCallback |
3. Update 对象的创建与入队
3.1 dispatchSetStateInternal 详解
文件位置:packages/react-reconciler/src/ReactFiberHooks.js:3629
function dispatchSetStateInternal<S, A>(
fiber: Fiber, // ProductList 的 Fiber
queue: UpdateQueue<S, A>, // filterCategory 的队列
action: A, // 'electronics'
lane: Lane, // DefaultLane
): boolean {
// ========== 步骤1:创建 Update 对象 ==========
const update: Update<S, A> = {
lane, // DefaultLane
revertLane: NoLane, // 用于 Suspense 回退
gesture: null, // 手势相关(实验性)
action, // 'electronics'
hasEagerState: false, // 是否已预计算
eagerState: null, // 预计算结果
next: (null: any), // 下一个 Update(环形链表)
};
console.log('创建 Update 对象:', update);
// ========== 步骤2:检查是否在渲染阶段 ==========
if (isRenderPhaseUpdate(fiber)) {
// 在组件渲染过程中调用了 setState
// 例如:在 render 函数体中直接调用 setState
enqueueRenderPhaseUpdate(queue, update);
return false; // 不调度新的更新
}
// ========== 步骤3:尝试"预计算优化" ==========
const alternate = fiber.alternate; // current 树的对应节点
// 条件:当前没有待处理的更新
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// 可以尝试预先计算新状态
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
try {
const currentState: S = (queue.lastRenderedState: any); // 'all'
// 计算新状态
const eagerState = lastRenderedReducer(currentState, action);
// basicStateReducer('all', 'electronics') → 'electronics'
console.log('预计算新状态:', eagerState);
// 保存预计算结果
update.hasEagerState = true;
update.eagerState = eagerState; // 'electronics'
// 如果新旧状态相同,可以跳过更新
if (is(eagerState, currentState)) {
// 'electronics' === 'all' → false
// 不相同,需要继续更新
} else {
// 我们的案例:状态不同,继续执行
console.log('状态发生变化,需要更新');
}
} catch (error) {
// 计算出错,继续正常流程
}
}
}
// ========== 步骤4:将 Update 加入并发更新队列 ==========
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
console.log('Update 已加入队列');
console.log('FiberRoot:', root);
// ========== 步骤5:调度更新 ==========
if (root !== null) {
// 触发 Fiber 树的重新渲染
scheduleUpdateOnFiber(root, fiber, lane);
// 处理 transition 相关逻辑
entangleTransitionUpdate(root, queue, lane);
return true; // 已调度更新
}
return false; // 未调度更新
}
执行后的状态:
update = {
lane: DefaultLane,
revertLane: NoLane,
gesture: null,
action: 'electronics', // ← 新值
hasEagerState: true, // ← 已预计算
eagerState: 'electronics', // ← 预计算结果
next: null,
}
3.2 enqueueConcurrentHookUpdate:入队逻辑
文件位置:packages/react-reconciler/src/ReactFiberConcurrentUpdates.js
export function enqueueConcurrentHookUpdate<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
update: Update<S, A>,
lane: Lane,
): FiberRoot | null {
// 步骤1:获取 Hook 更新队列
const concurrentQueue: ConcurrentQueue = (queue: any);
const concurrentUpdate: ConcurrentUpdate = (update: any);
// 步骤2:将 Update 加入内部队列
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
// 步骤3:返回 FiberRoot
return getRootForUpdatedFiber(fiber);
}
function enqueueUpdate(
fiber: Fiber,
queue: ConcurrentQueue | null,
update: ConcurrentUpdate | null,
lane: Lane,
) {
// 将 Update 添加到 queue.pending(环形链表)
// 第一次添加
if (queue.pending === null) {
// 创建环形链表:update.next = update(指向自己)
update.next = update;
} else {
// 插入到环形链表
update.next = queue.pending.next;
queue.pending.next = update;
}
// pending 始终指向最后一个 Update
queue.pending = update;
}
环形链表结构:
第一次调用 setState('electronics'):
queue.pending → Update1
↓
next → Update1 (指向自己)
第二次调用 setState('books'):
queue.pending → Update2
↓
next → Update1
↓
next → Update2 (形成环)
4. 调度更新的触发(和首次更新大部分差不多)
4.1 scheduleUpdateOnFiber:调度入口
文件位置:packages/react-reconciler/src/ReactFiberWorkLoop.js
export function scheduleUpdateOnFiber(
root: FiberRoot, // 应用的根节点
fiber: Fiber, // ProductList 的 Fiber
lane: Lane, // DefaultLane
): void {
// ========== 步骤1:检查无限循环 ==========
if (__DEV__) {
// 防止在 render 中无限调用 setState
if (
nestedUpdateCount > NESTED_UPDATE_LIMIT &&
root === rootWithNestedUpdates
) {
throw new Error(
'Maximum update depth exceeded. This can happen when a ' +
'component repeatedly calls setState inside componentWillUpdate ' +
'or componentDidUpdate.'
);
}
}
// ========== 步骤2:标记根节点有更新 ==========
markRootUpdated(root, lane);
console.log('标记 FiberRoot 有待处理的更新');
console.log('Lane:', lane);
// ========== 步骤3:检查是否在渲染中 ==========
if (
(executionContext & RenderContext) !== NoContext &&
root === workInProgressRoot
) {
// 正在渲染当前树,标记工作中的 Fiber
workInProgressRootDidSkipSuspendedSiblings = true;
}
// ========== 步骤4:确保根节点被调度 ==========
ensureRootIsScheduled(root);
console.log('调度完成');
}
4.2 markRootUpdated:标记更新
function markRootUpdated(root: FiberRoot, updateLane: Lane) {
// 将新的 lane 添加到 root.pendingLanes
root.pendingLanes |= updateLane;
// 如果不是过渡 lane,清除 suspended lanes
if (updateLane !== IdleLane) {
root.suspendedLanes = NoLanes;
root.pingedLanes = NoLanes;
}
// 标记根节点有未提交的更新
root.entangledLanes |= updateLane;
console.log('root.pendingLanes:', lanesToString(root.pendingLanes));
}
FiberRoot 的状态变化:
// 调用 setState 前
root.pendingLanes = NoLanes (0b0000000000000000000000000000000)
// 调用 setState 后
root.pendingLanes = DefaultLane (0b0000000000000000000000000000100)
4.3 ensureRootIsScheduled:核心调度逻辑
文件位置:packages/react-reconciler/src/ReactFiberWorkLoop.js
function ensureRootIsScheduled(root: FiberRoot): void {
// ========== 步骤1:标记饥饿的 lanes ==========
const currentTime = now();
markStarvedLanesAsExpired(root, currentTime);
// 将等待太久的低优先级任务提升为高优先级
// ========== 步骤2:获取下一个要处理的 lanes ==========
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
);
console.log('下一个要处理的 lanes:', lanesToString(nextLanes));
// ========== 步骤3:如果没有待处理的工作 ==========
if (nextLanes === NoLanes) {
// 没有更新需要处理
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode);
}
root.callbackNode = null;
root.callbackPriority = NoLane;
return;
}
// ========== 步骤4:获取最高优先级 ==========
const newCallbackPriority = getHighestPriorityLane(nextLanes);
const existingCallbackPriority = root.callbackPriority;
console.log('新的优先级:', newCallbackPriority);
console.log('现有优先级:', existingCallbackPriority);
// ========== 步骤5:检查是否可以复用现有调度 ==========
if (
existingCallbackPriority === newCallbackPriority &&
// 特殊情况处理...
) {
// 优先级相同,复用现有调度
console.log('复用现有调度');
return;
}
// ========== 步骤6:取消旧的调度 ==========
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode);
}
// ========== 步骤7:调度新任务 ==========
let newCallbackNode;
if (includesSyncLane(newCallbackPriority)) {
// 同步优先级(Legacy 模式或 flushSync)
console.log('使用同步调度');
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
// 立即刷新同步队列
if (supportsMicrotasks) {
scheduleMicrotask(flushSyncWorkOnRootsScheduledDuringFlush);
} else {
scheduleCallback(ImmediatePriority, flushSyncWorkOnRootsScheduledDuringFlush);
}
newCallbackNode = null;
} else {
// 并发模式
console.log('使用并发调度');
// 将 Lane 转换为 Scheduler 优先级
const schedulerPriorityLevel = lanesToSchedulerPriority(newCallbackPriority);
console.log('Scheduler 优先级:', schedulerPriorityLevel);
// 调度并发任务
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root)
);
}
// ========== 步骤8:保存调度信息 ==========
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
console.log('调度已安排,等待执行');
}
Lane 到 Scheduler 优先级的映射:
function lanesToSchedulerPriority(lanes: Lanes): SchedulerPriority {
const lane = getHighestPriorityLane(lanes);
if ((lane & SyncLane) !== NoLane) {
return ImmediatePriority; // 99
}
if ((lane & InputContinuousLane) !== NoLane) {
return UserBlockingPriority; // 98
}
if ((lane & DefaultLane) !== NoLane) {
return NormalPriority; // 97
}
if ((lane & IdleLane) !== NoLane) {
return IdlePriority; // 95
}
return NormalPriority;
}
5. 优先级计算与Lane模型
5.1 什么是 Lane
Lane 是 React 18 引入的优先级模型,使用 31 位二进制数 表示不同的优先级。
// packages/react-reconciler/src/ReactFiberLane.js
export const NoLane: Lane = 0b0000000000000000000000000000000;
export const NoLanes: Lanes = 0b0000000000000000000000000000000;
export const SyncLane: Lane = 0b0000000000000000000000000000010;
export const SyncHydrationLane: Lane = 0b0000000000000000000000000000001;
export const InputContinuousLane: Lane = 0b0000000000000000000000000001000;
export const InputContinuousHydrationLane: Lane = 0b0000000000000000000000000000100;
export const DefaultLane: Lane = 0b0000000000000000000000000100000;
export const DefaultHydrationLane: Lane = 0b0000000000000000000000000010000;
// 过渡优先级(共 15 个)
export const TransitionLane1: Lane = 0b0000000000000000000000001000000;
export const TransitionLane2: Lane = 0b0000000000000000000000010000000;
// ... TransitionLane3 - TransitionLane15
// 其他
export const RetryLane1: Lane = 0b0000000001000000000000000000000;
export const IdleLane: Lane = 0b0100000000000000000000000000000;
export const OffscreenLane: Lane = 0b1000000000000000000000000000000;
5.2 Lane 的优势
相比旧的 expirationTime 模型:
| 特性 | expirationTime | Lane |
|---|---|---|
| 表示方式 | 数字(时间戳) | 二进制位掩码 |
| 优先级数量 | 有限 | 31 个独立优先级 |
| 批处理 | 困难 | 简单(位运算) |
| 饥饿问题 | 需要特殊处理 | 自然支持 |
| 优先级插队 | 复杂 | 简单(清除位) |
Lane 的操作:
// 添加 lane
lanes |= newLane; // 0b0100 | 0b0010 = 0b0110
// 移除 lane
lanes &= ~removedLane; // 0b0110 & ~0b0010 = 0b0100
// 检查是否包含
(lanes & targetLane) !== NoLanes;
// 获取最高优先级 lane
function getHighestPriorityLane(lanes: Lanes): Lane {
return lanes & -lanes; // 位运算技巧:获取最右边的 1
// 例如:0b0110 & -0b0110 = 0b0010
}
5.3 getNextLanes:选择下一个处理的 Lane
文件位置:packages/react-reconciler/src/ReactFiberLane.js
export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
// 步骤1:获取所有待处理的 lanes
const pendingLanes = root.pendingLanes;
if (pendingLanes === NoLanes) {
return NoLanes; // 没有待处理的更新
}
let nextLanes = NoLanes;
// 步骤2:获取非空闲的 lanes
const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
if (nonIdlePendingLanes !== NoLanes) {
// 步骤3:排除被阻塞的 lanes
const nonIdleUnblockedLanes = nonIdlePendingLanes & ~root.suspendedLanes;
if (nonIdleUnblockedLanes !== NoLanes) {
// 步骤4:选择最高优先级
nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
} else {
// 所有非空闲 lanes 都被阻塞,选择被 ping 的 lanes
const nonIdlePingedLanes = nonIdlePendingLanes & root.pingedLanes;
if (nonIdlePingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
}
}
} else {
// 只有空闲 lanes
const unblockedLanes = pendingLanes & ~root.suspendedLanes;
if (unblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(unblockedLanes);
} else if (pingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(pingedLanes);
}
}
// 步骤5:如果没有选中任何 lane,返回 NoLanes
if (nextLanes === NoLanes) {
return NoLanes;
}
// 步骤6:处理纠缠的 lanes(entangled lanes)
// 某些 lanes 必须一起处理
nextLanes = mergeLanes(nextLanes, root.entangledLanes & nextLanes);
return nextLanes;
}
6. Render阶段的完整流程
6.1 performConcurrentWorkOnRoot:并发工作入口
文件位置:packages/react-reconciler/src/ReactFiberWorkLoop.js
function performConcurrentWorkOnRoot(
root: FiberRoot,
didTimeout: boolean, // 是否超时
): RenderTaskFn | null {
console.log('========== 开始并发渲染 ==========');
// ========== 步骤1:准备渲染 ==========
const originalCallbackNode = root.callbackNode;
// 执行可能的被动效果(useEffect)
const didFlushPassiveEffects = flushPassiveEffects();
if (didFlushPassiveEffects) {
// 可能产生了新的更新
if (root.callbackNode !== originalCallbackNode) {
// 调度被更改,退出
return null;
}
}
// ========== 步骤2:获取要渲染的 lanes ==========
const lanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
);
if (lanes === NoLanes) {
// 没有工作要做
return null;
}
console.log('渲染 lanes:', lanesToString(lanes));
// ========== 步骤3:判断是否使用并发模式 ==========
const shouldTimeSlice =
!includesBlockingLane(root, lanes) && // 不包含阻塞 lane
!includesExpiredLane(root, lanes) && // 不包含过期 lane
!didTimeout; // 没有超时
console.log('是否使用时间切片:', shouldTimeSlice);
// ========== 步骤4:执行渲染 ==========
let exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes) // 可中断渲染
: renderRootSync(root, lanes); // 同步渲染
console.log('渲染退出状态:', exitStatus);
// ========== 步骤5:处理渲染结果 ==========
if (exitStatus !== RootInProgress) {
// 渲染完成或出错
if (exitStatus === RootDidNotComplete) {
// 渲染未完成(被中断)
markRootSuspended(root, lanes, NoLane, false);
}
if (exitStatus === RootFatalErrored) {
// 致命错误
prepareFreshStack(root, NoLanes);
markRootSuspended(root, lanes, NoLane, false);
ensureRootIsScheduled(root);
return null;
}
if (exitStatus === RootErrored) {
// 可恢复的错误,重试
// ...
}
// ========== 步骤6:检查是否完成 ==========
if (exitStatus === RootCompleted) {
console.log('渲染完成,准备提交');
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
// ========== 步骤7:提交更新 ==========
finishConcurrentRender(root, exitStatus, finishedWork, lanes);
}
}
// ========== 步骤8:确保根节点被调度 ==========
ensureRootIsScheduled(root);
// 返回继续函数(如果还有工作)
if (root.callbackNode === originalCallbackNode) {
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}
6.2 renderRootConcurrent:可中断渲染
function renderRootConcurrent(root: FiberRoot, lanes: Lanes): RootExitStatus {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
// ========== 步骤1:准备新的工作栈 ==========
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
prepareFreshStack(root, lanes);
console.log('准备新的 Fiber 树');
}
console.log('========== 开始工作循环 ==========');
// ========== 步骤2:工作循环(可中断) ==========
outer: do {
try {
if (
workInProgressSuspendedReason !== NotSuspended &&
workInProgress !== null
) {
// 处理 Suspense
// ...
}
workLoopConcurrent(); // ← 核心工作循环
break;
} catch (thrownValue) {
// 处理错误
handleThrow(root, thrownValue);
}
} while (true);
// ========== 步骤3:重置状态 ==========
executionContext = prevExecutionContext;
// ========== 步骤4:返回退出状态 ==========
if (workInProgress !== null) {
// 还有未完成的工作(被中断)
return RootInProgress;
} else {
// 工作完成
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
}
6.3 workLoopConcurrent:核心工作循环
function workLoopConcurrent() {
// 循环处理 Fiber 节点,直到没有工作或需要让出控制权
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
// shouldYield:检查是否应该让出控制权
function shouldYield(): boolean {
const currentTime = now();
if (currentTime >= deadline) {
// 超过时间片(默认 5ms)
return true;
}
// 检查是否有更高优先级的任务
if (needsPaint || scheduling.isInputPending()) {
return true;
}
return false;
}
6.4 performUnitOfWork:处理单个 Fiber
function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate;
console.log('处理 Fiber:', getComponentNameFromFiber(unitOfWork));
// ========== 步骤1:beginWork(递阶段) ==========
let next = beginWork(current, unitOfWork, renderLanes);
// 更新 props
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// 没有子节点,完成当前节点
completeUnitOfWork(unitOfWork);
} else {
// 继续处理子节点
workInProgress = next;
}
}
6.5 beginWork:处理组件(关键!)
文件位置:packages/react-reconciler/src/ReactFiberBeginWork.js
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
console.log('beginWork:', workInProgress.tag);
// ========== 优化:bailout 检查 ==========
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
(__DEV__ && workInProgress.type !== current.type)
) {
// Props 变化,需要更新
didReceiveUpdate = true;
} else {
// Props 未变化,检查是否可以跳过
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (!hasScheduledUpdateOrContext) {
// 可以跳过这个组件
console.log('跳过组件(bailout)');
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
}
// 清除 lanes(表示正在处理)
workInProgress.lanes = NoLanes;
// ========== 根据组件类型处理 ==========
switch (workInProgress.tag) {
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps = workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case ClassComponent: {
// 处理类组件
// ...
}
case HostRoot: {
// 处理根节点
return updateHostRoot(current, workInProgress, renderLanes);
}
case HostComponent: {
// 处理原生 DOM 元素
return updateHostComponent(current, workInProgress, renderLanes);
}
// ... 其他 32 种类型
}
}
6.6 updateFunctionComponent:处理函数组件
这是我们的 ProductList 组件被处理的地方!
function updateFunctionComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any, // ProductList 函数
nextProps: any,
renderLanes: Lanes,
): Fiber | null {
console.log('更新函数组件:', Component.name);
// ========== 步骤1:准备上下文 ==========
let context;
if (!disableLegacyContext) {
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
context = getMaskedContext(workInProgress, unmaskedContext);
}
// ========== 步骤2:调用组件函数(renderWithHooks) ==========
let nextChildren = renderWithHooks(
current,
workInProgress,
Component, // ProductList
nextProps, // props
context,
renderLanes,
);
console.log('组件返回的子元素:', nextChildren);
// ========== 步骤3:检查是否可以复用 ==========
if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// ========== 步骤4:协调子元素 ==========
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
// ========== 步骤5:返回子 Fiber ==========
return workInProgress.child;
}
6.7 renderWithHooks:执行 Hooks(核心!)
文件位置:packages/react-reconciler/src/ReactFiberHooks.js
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
console.log('========== renderWithHooks ==========');
// ========== 步骤1:设置全局变量 ==========
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
// 清除状态(将在组件调用时重新构建)
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
// ========== 步骤2:设置正确的 Dispatcher ==========
ReactSharedInternals.H =
current === null || current.memoizedState === null
? HooksDispatcherOnMount // 首次渲染
: HooksDispatcherOnUpdate; // 更新
console.log('Dispatcher:', current === null ? 'Mount' : 'Update');
// ========== 步骤3:调用组件函数 ==========
console.log('调用组件函数:', Component.name);
let children = Component(props, secondArg); // ← ProductList(props)
console.log('组件执行完成');
// ========== 步骤4:检查是否有渲染阶段更新 ==========
if (didScheduleRenderPhaseUpdateDuringThisPass) {
children = renderWithHooksAgain(workInProgress, Component, props, secondArg);
}
// ========== 步骤5:完成渲染 ==========
finishRenderingHooks(current, workInProgress, Component);
return children;
}
当执行 Component(props) 时,发生了什么?(关键)
// Component = ProductList
// 执行 ProductList(props)
function ProductList(props) {
// 第1个 Hook
const [products, setProducts] = useState([]);
// ↓ 调用 dispatcher.useState([])
// ↓ 因为是 Update,调用 updateState
// ↓ 从 Hook 链表获取第1个 Hook
// ↓ 处理更新队列(如果有)
// ↓ 返回 [products, setProducts]
// 第2个 Hook
const [filterCategory, setFilterCategory] = useState('all');
// ↓ 调用 dispatcher.useState('all')
// ↓ 从 Hook 链表获取第2个 Hook
// ↓ 处理更新队列 ← 这里有我们的 Update!
// ↓ queue.pending = Update { action: 'electronics' }
// ↓ 计算新状态:basicStateReducer('all', 'electronics') → 'electronics'
// ↓ 返回 ['electronics', setFilterCategory]
// 第3个 Hook
const [sortBy, setSortBy] = useState('price');
// ↓ 从 Hook 链表获取第3个 Hook
// ↓ 没有更新,返回旧状态
// 第4个 Hook
const [loading, setLoading] = useState(false);
// ↓ 从 Hook 链表获取第4个 Hook
// 第5个 Hook
useEffect(() => {
fetchProducts();
}, [filterCategory, sortBy]);
// ↓ 调用 dispatcher.useEffect
// ↓ 比较依赖:[filterCategory, sortBy]
// ↓ 旧依赖:['all', 'price']
// ↓ 新依赖:['electronics', 'price']
// ↓ filterCategory 变化!标记 HookHasEffect
// ↓ 这个 effect 会在 Commit 阶段执行
// ... 组件逻辑
return (/* JSX */);
}
6.8 reconcileChildren:协调子元素(Diff 算法)
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes,
) {
if (current === null) {
// 首次渲染:直接创建子 Fiber
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
// 更新:进行 Diff
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes,
);
}
}
Diff 算法的三大策略:
- Tree Diff:只比较同层节点,不跨层比较
- Component Diff:相同类型的组件才会继续比较
- Element Diff:使用 key 优化列表比较
6.5 Render 阶段性能优化:Lane 标记与 Bailout 机制
疑问:此时,我们不禁想问,假如某个组件 A 调用 setState,会遍历整个项目的所有 Fiber 吗?
答案:不会!React 通过 Lane 标记机制和 bailout 优化,只处理必要的 Fiber 节点。
6.5.1 setState 如何避免遍历整个 Fiber 树
核心机制
组件 A 调用 setState
↓
1. 标记路径:从组件 A 向上标记到根节点的所有 Fiber 的 lanes
↓
2. 调度更新:从根节点开始遍历
↓
3. Bailout 优化:跳过没有标记的子树
↓
结果:只处理组件 A 及其子树,其他组件被跳过
示例:复杂组件树的更新
<Root>
<App>
<Sidebar>
<Menu /> // ← 这些组件不会被处理
<UserInfo /> // ← 这些组件不会被处理
</Sidebar>
<Main>
<Header />
<Content>
<ProductList> // ← 组件 A 在这里调用 setState
<ProductItem />
<ProductItem />
</ProductList>
</Content>
<Footer />
</Main>
</App>
</Root>
阶段 1:标记路径(markRootUpdated)
当 ProductList 组件调用 setState 时:
// 在 dispatchSetState 中
function dispatchSetState(fiber, queue, action) {
// 1. 创建 Update
const update = { lane: SyncLane, action, next: null };
queue.pending = update;
// 2. 调度更新
const root = enqueueConcurrentHookUpdate(fiber, queue, update, SyncLane);
if (root !== null) {
// 关键:标记从 ProductList 到根节点的路径
scheduleUpdateOnFiber(root, fiber, SyncLane);
}
}
标记过程:
// 1. setState 时:暂存 Update,只标记当前 Fiber
// packages/react-reconciler/src/ReactFiberConcurrentUpdates.js
function enqueueUpdate(fiber, queue, update, lane) {
// 暂存到数组,稍后批量处理
concurrentQueues[concurrentQueuesIndex++] = fiber;
concurrentQueues[concurrentQueuesIndex++] = lane;
// 只标记当前 Fiber
fiber.lanes = mergeLanes(fiber.lanes, lane);
// 此时不向上标记 childLanes
}
// 2. 渲染开始前:批量向上标记 childLanes
// packages/react-reconciler/src/ReactFiberWorkLoop.js
renderRootConcurrent(root, lanes) {
prepareFreshStack(root, lanes);
↓
// packages/react-reconciler/src/ReactFiberConcurrentUpdates.js
finishQueueingConcurrentUpdates() {
while (i < endIndex) {
const fiber = concurrentQueues[i++];
const lane = concurrentQueues[i++];
// 在这里调用,向上标记所有父 Fiber
markUpdateLaneFromFiberToRoot(fiber, update, lane);
}
}
}
// 3. 向上标记函数
function markUpdateLaneFromFiberToRoot(sourceFiber, update, lane) {
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
// 向上遍历,标记所有父 Fiber 的 childLanes
let parent = sourceFiber.return;
while (parent !== null) {
parent.childLanes = mergeLanes(parent.childLanes, lane);
const alternate = parent.alternate;
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
}
parent = parent.return;
}
}
标记结果(Fiber 树的 lanes 标记):
RootFiber
├─ lanes: NoLanes
├─ childLanes: SyncLane 标记:子树有更新
└─ child → App Fiber
├─ lanes: NoLanes
├─ childLanes: SyncLane 标记:子树有更新
└─ child → Sidebar / Main
↓
Main Fiber
├─ lanes: NoLanes
├─ childLanes: SyncLane 标记:子树有更新
└─ child → Header / Content / Footer
↓
Content Fiber
├─ lanes: NoLanes
├─ childLanes: SyncLane 标记:子树有更新
└─ child → ProductList
↓
ProductList Fiber
├─ lanes: SyncLane 自己有更新
├─ childLanes: NoLanes
└─ child → ProductItem...
Sidebar Fiber(未标记)
├─ lanes: NoLanes
├─ childLanes: NoLanes ← 没有更新
└─ child → Menu / UserInfo
阶段 2:遍历 Fiber 树(bailout 优化)
调度开始后,从根节点遍历:
// packages/react-reconciler/src/ReactFiberBeginWork.js
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
// ========== 优化1:检查当前 Fiber 是否需要更新 ==========
const updateLanes = workInProgress.lanes;
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
// 检查是否可以跳过(bailout)
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
// 强制更新标志
workInProgress.type !== current.type
) {
// Props 改变了,需要更新
didReceiveUpdate = true;
} else {
// ========== 关键检查:子树是否有更新 ==========
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (!hasScheduledUpdateOrContext) {
// 关键,Bailout 优化:跳过当前 Fiber 及其子树
didReceiveUpdate = false;
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
didReceiveUpdate = false;
}
}
// 清除 lanes(已经处理过了)
workInProgress.lanes = NoLanes;
// 继续处理当前 Fiber
switch (workInProgress.tag) {
case FunctionComponent:
return updateFunctionComponent(current, workInProgress, ...);
// ...
}
}
checkScheduledUpdateOrContext:检查是否需要更新
function checkScheduledUpdateOrContext(
current: Fiber,
renderLanes: Lanes,
): boolean {
// 检查当前 Fiber 的 lanes 是否包含当前渲染的 lanes
const updateLanes = current.lanes;
if (includesSomeLane(updateLanes, renderLanes)) {
return true; // 当前 Fiber 有更新
}
// 检查子树是否有更新
const childLanes = current.childLanes;
if (includesSomeLane(childLanes, renderLanes)) {
return true; // 子树有更新
}
return false; // 没有更新,可以 bailout
}
bailoutOnAlreadyFinishedWork:跳过子树
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
if (current !== null) {
// 复用 current 树的子节点(不重新创建)
workInProgress.child = current.child;
}
// ========== 关键:检查子树是否需要处理 ==========
if (!includesSomeLane(workInProgress.childLanes, renderLanes)) {
// 子树也没有更新,直接返回 null
// 不会继续遍历子树,直接跳过
return null;
}
// 子树有更新,克隆子节点(继续遍历子树)
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
完整的遍历过程(以示例为例)
========== 开始 Render 阶段 ==========
workLoopConcurrent()
1. performUnitOfWork(RootFiber)
├─ beginWork(RootFiber)
│ ├─ childLanes: SyncLane(子树有更新)
│ └─ 返回 App Fiber ✅ 继续处理
↓
2. performUnitOfWork(App Fiber)
├─ beginWork(App Fiber)
│ ├─ lanes: NoLanes(自己无更新)
│ ├─ childLanes: SyncLane(子树有更新)
│ ├─ Props 没变
│ └─ 返回 Sidebar Fiber 继续处理
↓
3. performUnitOfWork(Sidebar Fiber)
├─ beginWork(Sidebar Fiber)
│ ├─ lanes: NoLanes(自己无更新)
│ ├─ childLanes: NoLanes(子树无更新)★★★
│ ├─ bailoutOnAlreadyFinishedWork
│ └─ 返回 null 跳过整个 Sidebar 子树
↓
4. performUnitOfWork(Main Fiber) ← 处理 Sidebar 的兄弟节点
├─ beginWork(Main Fiber)
│ ├─ lanes: NoLanes
│ ├─ childLanes: SyncLane(子树有更新)
│ └─ 返回 Header Fiber 继续处理
↓
5. performUnitOfWork(Header Fiber)
├─ beginWork(Header Fiber)
│ ├─ childLanes: NoLanes
│ └─ bailout,返回 null 跳过
↓
6. performUnitOfWork(Content Fiber) ← Header 的兄弟
├─ beginWork(Content Fiber)
│ ├─ childLanes: SyncLane
│ └─ 返回 ProductList Fiber
↓
7. performUnitOfWork(ProductList Fiber)
├─ beginWork(ProductList Fiber)
│ ├─ lanes: SyncLane 自己有更新
│ ├─ 重新调用组件函数 ProductList()
│ ├─ 计算新状态
│ ├─ reconcileChildren(Diff 子节点)
│ └─ 返回 ProductItem Fiber
↓
8. performUnitOfWork(ProductItem Fiber)
├─ beginWork(ProductItem Fiber)
│ ├─ Props 可能变了
│ └─ 重新渲染
↓
9. performUnitOfWork(Footer Fiber) ← Content 的兄弟
├─ beginWork(Footer Fiber)
│ ├─ childLanes: NoLanes
│ └─ bailout 跳过
统计:实际处理的 Fiber 数量
组件树总共 14 个 Fiber:
- Root, App, Sidebar, Menu, UserInfo, Main, Header, Content, ProductList, ProductItem × 2, Footer (共 12 个)
实际处理的 Fiber:
- Root
- App
- Sidebar (bailout,但需要检查)
- Main
- Header (bailout)
- Content
- ProductList (重新渲染)
- ProductItem × 2 (可能重新渲染)
- Footer (bailout)
跳过的 Fiber:
- Menu (Sidebar bailout,整个子树跳过)
- UserInfo (Sidebar bailout,整个子树跳过)
优化效果:
- 理论上需要遍历 14 个 Fiber
- 实际上只处理了约 10 个 Fiber(根据 Props 对比可能更少)
- Menu 和 UserInfo 的整个子树被完全跳过
Lane 标记的传播路径
// setState 调用点:ProductList Fiber
ProductList Fiber
↑
└─ lanes: SyncLane ★ 自己标记
向上传播 childLanes:
ProductList.return → Content Fiber
↑
└─ childLanes |= SyncLane ★
Content.return → Main Fiber
↑
└─ childLanes |= SyncLane ★
Main.return → App Fiber
↑
└─ childLanes |= SyncLane ★
App.return → RootFiber
↑
└─ childLanes |= SyncLane ★
// 标记完成,形成一条从 ProductList 到 Root 的"更新路径"
为什么必须从根节点开始遍历?
原因 1:保证顺序
// 如果直接从 ProductList 开始:
// 1. 无法处理 ProductList 上面的 Context 变化
// 2. 无法处理 ProductList 上面的 Props 传递
// 3. Fiber 树的遍历顺序会错乱
// 正确做法:从根开始,通过 bailout 跳过不需要的分支
原因 2:支持优先级中断
// 低优先级更新进行到一半,来了高优先级更新
// 需要从根重新开始,使用新的优先级
prepareFreshStack(root, highPriorityLanes);
workLoopConcurrent();
原因 3:Context 和 Props 传递
<ThemeContext.Provider value={theme}> // ← 可能影响子树
<App>
<Main>
<ProductList /> // ← 可能依赖 theme
</Main>
</App>
</ThemeContext.Provider>
// 必须从 Provider 开始处理,才能正确传递 Context
优化总结
| 机制 | 作用 | 实现方式 |
|---|---|---|
| Lane 标记 | 标记哪些 Fiber 需要更新 | finishQueueingConcurrentUpdates 中调用 markUpdateLaneFromFiberToRoot |
| childLanes 传播 | 标记更新路径 | 向上标记所有父 Fiber 的 childLanes |
| bailout 优化 | 跳过不需要更新的子树 | bailoutOnAlreadyFinishedWork |
| Props 对比 | 判断组件是否需要重新渲染 | oldProps !== newProps |
完整的更新流程图
用户在 ProductList 中调用 setState
↓
1. 创建 Update 对象
↓
2. 标记 ProductList.lanes = SyncLane
↓
3. 向上传播 childLanes
ProductList → Content → Main → App → Root
↓
4. 调度更新(从根开始)
↓
5. workLoop 遍历 Fiber 树
├─ Root: childLanes 有 SyncLane → 继续
├─ App: childLanes 有 SyncLane → 继续
├─ Sidebar: childLanes 为空 → bailout(跳过 Menu, UserInfo)
├─ Main: childLanes 有 SyncLane → 继续
├─ Header: childLanes 为空 → bailout
├─ Content: childLanes 有 SyncLane → 继续
├─ ProductList: lanes 有 SyncLane → 重新渲染 ★★★
│ └─ 调用 ProductList(),计算新状态,reconcileChildren
├─ ProductItem: 根据 Props 决定是否重新渲染
└─ Footer: childLanes 为空 → bailout
↓
6. Commit 阶段:只更新有变化的 DOM
6.5.2 那么有人就会问了,那组件内部的子组件会被标记和遍历吗
假设:当组件 A 调用 setState,A 内部的子组件是否有 Lane 标记?是否会遍历 A 的所有子组件?
答案:
- A 的子组件不会被标记 lanes(Lane 只向上传播,不向下)
- 但 A 的所有子组件都会被遍历(因为 A 重新渲染会调用 reconcileChildren)
- 子组件是否重新渲染取决于 Props 是否变化
示例:ProductList 及其子组件
function ProductList() {
const [filter, setFilter] = useState('all'); // ← setState 在这里
return (
<div>
<FilterButton
active={filter === 'all'}
onClick={() => setFilter('all')} // ← 子组件 1
/>
<ProductItem id={1} /> // ← 子组件 2
<ProductItem id={2} /> // ← 子组件 3
<ProductItem id={3} /> // ← 子组件 4
<Footer /> // ← 子组件 5
</div>
);
}
阶段 1:Lane 标记(只向上,不向下)
// 用户调用 setFilter('all')
ProductList Fiber
├─ lanes: SyncLane ★★★ 自己被标记
└─ childLanes: NoLanes
向上传播(childLanes):
↑
Content Fiber
├─ lanes: NoLanes
└─ childLanes: SyncLane 传播
Main Fiber
├─ lanes: NoLanes
└─ childLanes: SyncLane 传播
App Fiber
├─ lanes: NoLanes
└─ childLanes: SyncLane 传播
Root Fiber
├─ lanes: NoLanes
└─ childLanes: SyncLane 传播
---
向下查看(子组件):
ProductList Fiber
├─ lanes: SyncLane
└─ child → div Fiber
├─ lanes: NoLanes 子组件没有被标记
└─ child → FilterButton Fiber
├─ lanes: NoLanes 没有标记
└─ sibling → ProductItem Fiber
├─ lanes: NoLanes 没有标记
└─ sibling → ...
关键点:Lane 标记只向上传播(通过 childLanes),不向下传播
阶段 2:遍历过程(子组件会被遍历)
// 从根开始遍历,最终到达 ProductList...
performUnitOfWork(ProductList Fiber)
↓
beginWork(ProductList Fiber)
├─ lanes: SyncLane 自己有更新
├─ 调用 ProductList() 函数 重新执行
│ ├─ useState 计算新状态: filter = 'all'
│ └─ 返回新的 ReactElement 树
↓
reconcileChildren(ProductList, newChildren)
├─ 比较旧的子 Fiber 和新的 ReactElement
├─ 创建/复用子 Fiber 节点
└─ 返回 div Fiber 返回第一个子节点
↓
performUnitOfWork(div Fiber) // ← 继续遍历子组件
↓
beginWork(div Fiber)
├─ lanes: NoLanes 自己没有标记
├─ childLanes: NoLanes
├─ 检查 Props 是否变化
│ ├─ oldProps: { children: [...] }
│ └─ newProps: { children: [...] }
↓
如果 Props 没变:
└─ bailoutOnAlreadyFinishedWork
├─ 复用旧的子 Fiber
└─ 返回 FilterButton Fiber 继续遍历子节点
↓
performUnitOfWork(FilterButton Fiber) // ← 继续遍历
↓
beginWork(FilterButton Fiber)
├─ lanes: NoLanes
├─ 检查 Props 是否变化
│ ├─ oldProps: { active: false, onClick: fn1 }
│ └─ newProps: { active: true, onClick: fn2 } Props 变了
↓
Props 变了,需要重新渲染:
├─ 调用 FilterButton(newProps)
├─ reconcileChildren
└─ 返回子 Fiber
↓
performUnitOfWork(ProductItem Fiber id=1)
↓
beginWork(ProductItem Fiber)
├─ lanes: NoLanes
├─ 检查 Props
│ ├─ oldProps: { id: 1 }
│ └─ newProps: { id: 1 } Props 没变
↓
Props 没变,bailout:
├─ 不调用 ProductItem 函数
├─ 复用旧的子 Fiber
└─ 返回子节点(如果有)
↓
performUnitOfWork(ProductItem Fiber id=2)
// 同上,Props 没变,bailout
↓
performUnitOfWork(ProductItem Fiber id=3)
// 同上,Props 没变,bailout
↓
performUnitOfWork(Footer Fiber)
// Props 没变,bailout
完整的遍历路径图
ProductList.setState()
↓
========== Lane 标记(向上) ==========
ProductList.lanes = SyncLane
ProductList → Content → Main → App → Root (childLanes)
========== 遍历开始(从根向下) ==========
Root → App → Main → Content → ProductList ★ 重新渲染
↓
ProductList() 执行
├─ useState 计算新状态
└─ 返回新的 JSX 树
↓
reconcileChildren ★ 创建/更新子 Fiber
↓
========== 遍历 ProductList 的子组件 ==========
1. div Fiber
├─ lanes: NoLanes (没有 Lane 标记)
├─ Props 对比 → 可能 bailout
└─ 遍历 (需要检查子节点)
2. FilterButton Fiber
├─ lanes: NoLanes (没有 Lane 标记)
├─ Props 对比 → active 变了
└─ 重新渲染
3. ProductItem Fiber × 3
├─ lanes: NoLanes (没有 Lane 标记)
├─ Props 对比 → 没变
└─ bailout (不重新渲染)
关键代码:beginWork 中的 Props 对比
// packages/react-reconciler/src/ReactFiberBeginWork.js
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
// ========== 关键检查 1:Props 是否变化 ==========
if (
oldProps !== newProps || // 引用对比(浅比较)
hasLegacyContextChanged() ||
workInProgress.type !== current.type
) {
// Props 变了,需要重新渲染
didReceiveUpdate = true;
} else {
// Props 没变,检查是否有 Lane 标记
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (!hasScheduledUpdateOrContext) {
// ========== 没有更新,可以 bailout ==========
didReceiveUpdate = false;
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
didReceiveUpdate = false;
}
} else {
// 首次渲染
didReceiveUpdate = false;
}
// 清除已处理的 lanes
workInProgress.lanes = NoLanes;
// ========== 根据组件类型处理 ==========
switch (workInProgress.tag) {
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
return updateFunctionComponent(
current,
workInProgress,
Component,
unresolvedProps,
renderLanes,
);
}
// ...
}
}
为什么子组件会被遍历但不一定重新渲染?
遍历(Traverse) 和 重新渲染(Re-render) 是两个不同的概念:
| 操作 | 含义 | 开销 |
|---|---|---|
| 遍历(performUnitOfWork) | 访问 Fiber 节点,执行 beginWork | 很小(只是函数调用和对比) |
| 重新渲染(调用组件函数) | 执行 Component(props),生成新的 ReactElement |
较大(执行用户代码、创建对象) |
| bailout | 跳过重新渲染,复用旧的子 Fiber | 最小(直接复用) |
示例:实际执行情况
function ProductList() {
console.log('ProductList 渲染'); // ← 会打印
const [filter, setFilter] = useState('all');
return (
<div>
<FilterButton
active={filter === 'all'} // ← Props 变了
onClick={() => setFilter('all')} // ← 新函数引用
/>
<ProductItem id={1} /> // ← Props 没变
</div>
);
}
function FilterButton({ active, onClick }) {
console.log('FilterButton 渲染'); // ← 会打印(Props 变了)
return <button className={active ? 'active' : ''} onClick={onClick} />;
}
function ProductItem({ id }) {
console.log('ProductItem 渲染', id); // ← 不会打印(bailout)
return <div>Product {id}</div>;
}
点击后的控制台输出:
ProductList 渲染
FilterButton 渲染
ProductItem 不会打印 → 因为 Props 没变,发生了 bailout
bailout 的条件
// packages/react-reconciler/src/ReactFiberBeginWork.js
// bailout 需要同时满足:
if (
oldProps === newProps && // 1. Props 引用相同(浅比较)
!hasLegacyContextChanged() && // 2. Context 没变
workInProgress.type === current.type && // 3. 组件类型没变
!includesSomeLane(renderLanes, updateLanes) // 4. 没有 Lane 标记
) {
// 可以 bailout
return bailoutOnAlreadyFinishedWork(...);
}
常见的破坏 bailout 的写法
function ProductList() {
const [filter, setFilter] = useState('all');
return (
<div>
{/* 错误: 每次渲染都创建新对象 → Props 总是变化 */}
<ProductItem data={{ id: 1, name: 'Product 1' }} />
{/* 错误: 每次渲染都创建新函数 → Props 总是变化 */}
<FilterButton onClick={() => setFilter('all')} />
{/* 错误: 每次渲染都创建新数组 → Props 总是变化 */}
<ProductList items={[1, 2, 3]} />
{/* 正确: 原始值不变 → 可以 bailout */}
<ProductItem id={1} name="Product 1" />
{/* 正确: 使用 useCallback 缓存函数 → 可以 bailout */}
<FilterButton onClick={handleClick} />
{/* 正确: 使用 useMemo 缓存对象 → 可以 bailout */}
<ProductItem data={memoizedData} />
</div>
);
}
优化:React.memo 和 useMemo
// 优化前:每次都重新渲染
function ProductItem({ id, name }) {
console.log('渲染', id);
return <div>{name}</div>;
}
// 优化后:Props 没变就不重新渲染
const ProductItem = React.memo(function ProductItem({ id, name }) {
console.log('渲染', id);
return <div>{name}</div>;
});
// React.memo 的原理:在 bailout 前额外做一次 Props 浅比较
// 默认使用 shallowEqual,也可以传入自定义比较函数
完整流程总结
ProductList.setState()
↓
【标记阶段】
├─ ProductList.lanes = SyncLane
├─ 向上传播 childLanes 到根
└─ 子组件不会被标记
↓
【遍历阶段】
├─ 从根开始遍历
├─ 通过 childLanes 找到 ProductList
├─ ProductList 重新渲染
│ └─ reconcileChildren → 更新子 Fiber
↓
【子组件处理】
├─ div Fiber
│ ├─ lanes: NoLanes (无标记)
│ ├─ Props 对比 → 可能 bailout
│ └─ 遍历 (需要检查子节点)
│
├─ FilterButton Fiber
│ ├─ lanes: NoLanes (无标记)
│ ├─ Props 对比 → active 变了
│ └─ 重新渲染
│
└─ ProductItem Fiber × 3
├─ lanes: NoLanes (无标记)
├─ Props 对比 → 没变
└─ bailout (不重新渲染)
性能对比
场景:ProductList 有 100 个 ProductItem 子组件
function ProductList() {
const [filter, setFilter] = useState('all');
return (
<div>
{Array.from({ length: 100 }, (_, i) => (
<ProductItem key={i} id={i} />
))}
</div>
);
}
没有优化(每次传递新对象):
<ProductItem key={i} data={{ id: i }} /> // ← 每次新对象
// 结果:
// - 101 个 Fiber 被遍历(ProductList + 100 个 ProductItem)
// - 101 个组件被重新渲染
// - 耗时:~10ms(假设)
有优化(Props 不变):
<ProductItem key={i} id={i} /> // ← 原始值不变
// 结果:
// - 101 个 Fiber 被遍历
// - 1 个组件被重新渲染(只有 ProductList)
// - 100 个组件 bailout
// - 耗时:~1ms(假设)
使用 React.memo:
const ProductItem = React.memo(function ProductItem({ id }) {
return <div>Product {id}</div>;
});
// 结果:
// - 101 个 Fiber 被遍历
// - 1 个组件被重新渲染
// - 100 个组件 bailout(React.memo 提前检查)
// - 耗时:~0.8ms(假设,memo 检查更轻量)
关键要点
| 问题 | 答案 |
|---|---|
| 子组件会被标记 lanes 吗? | 不会,lanes 只向上传播 |
| 子组件会被遍历吗? | 会,因为父组件重新渲染 |
| 子组件一定会重新渲染吗? | 不一定,取决于 Props 是否变化 |
| 如何避免子组件重新渲染? | 使用 React.memo、useMemo、useCallback |
| bailout 会完全跳过子树吗? | 部分跳过,仍会执行 beginWork,但不调用组件函数 |
| 遍历和渲染的区别? | 遍历:访问 Fiber;渲染:调用组件函数 |
7. Commit阶段的完整流程
7.1 finishConcurrentRender:准备提交
function finishConcurrentRender(
root: FiberRoot,
exitStatus: RootExitStatus,
finishedWork: Fiber,
lanes: Lanes,
): void {
console.log('========== 准备提交 ==========');
// 根据退出状态处理
switch (exitStatus) {
case RootCompleted: {
// 渲染完成,提交
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
workInProgressDeferredLane,
);
break;
}
// ... 其他状态
}
}
7.2 commitRoot:提交阶段入口
文件位置:packages/react-reconciler/src/ReactFiberWorkLoop.js
function commitRoot(
root: FiberRoot,
recoverableErrors: null | Array<CapturedValue<mixed>>,
transitions: Array<Transition> | null,
renderPriorityLevel: Lane,
): void {
console.log('========== 开始 Commit 阶段 ==========');
// ========== 步骤1:准备提交 ==========
const previousUpdateLanePriority = getCurrentUpdatePriority();
const prevTransition = ReactSharedInternals.T;
try {
ReactSharedInternals.T = null;
setCurrentUpdatePriority(DiscreteEventPriority);
// ========== 步骤2:执行提交 ==========
commitRootImpl(
root,
recoverableErrors,
transitions,
renderPriorityLevel,
);
} finally {
ReactSharedInternals.T = prevTransition;
setCurrentUpdatePriority(previousUpdateLanePriority);
}
}
7.3 commitRootImpl:提交核心逻辑
function commitRootImpl(
root: FiberRoot,
recoverableErrors: null | Array<CapturedValue<mixed>>,
transitions: Array<Transition> | null,
renderPriorityLevel: Lane,
): void {
const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;
console.log('Commit 的 Fiber 树:', finishedWork);
// ========== 步骤1:执行被动效果(useEffect) ==========
// 在开始之前先清理上一次的 effects
do {
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
// ========== 步骤2:准备提交 ==========
root.finishedWork = null;
root.finishedLanes = NoLanes;
const subtreeHasEffects =
(finishedWork.subtreeFlags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
const rootHasEffect =
(finishedWork.flags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
if (subtreeHasEffects || rootHasEffect) {
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
// ========== 阶段1:Before Mutation(DOM 变更前) ==========
console.log('===== Before Mutation 阶段 =====');
commitBeforeMutationEffects(root, finishedWork);
// ========== 阶段2:Mutation(DOM 变更) ==========
console.log('===== Mutation 阶段 =====');
commitMutationEffects(root, finishedWork, lanes);
// ========== 切换 current 树 ==========
root.current = finishedWork;
console.log('current 树已切换');
// ========== 阶段3:Layout(DOM 变更后) ==========
console.log('===== Layout 阶段 =====');
commitLayoutEffects(finishedWork, root, lanes);
executionContext = prevExecutionContext;
} else {
// 没有副作用,直接切换
root.current = finishedWork;
}
// ========== 步骤3:调度被动效果 ==========
const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
if (rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
// 调度异步执行 useEffect
scheduleCallback(NormalPriority, () => {
flushPassiveEffects();
return null;
});
}
// ========== 步骤4:清理工作 ==========
remainingLanes = root.pendingLanes;
// 确保有后续工作被调度
ensureRootIsScheduled(root);
// 处理错误
if (recoverableErrors !== null) {
// 报告可恢复的错误
// ...
}
console.log('========== Commit 完成 ==========');
}
7.4 Before Mutation 阶段
export function commitBeforeMutationEffects(
root: FiberRoot,
firstChild: Fiber,
): boolean {
focusedInstanceHandle = prepareForCommit(root.containerInfo);
nextEffect = firstChild;
commitBeforeMutationEffects_begin();
const shouldFire = commitBeforeMutationEffects_complete();
focusedInstanceHandle = null;
return shouldFire;
}
function commitBeforeMutationEffects_begin() {
while (nextEffect !== null) {
const fiber = nextEffect;
const child = fiber.child;
if (
(fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
child !== null
) {
// 继续遍历子节点
nextEffect = child;
} else {
commitBeforeMutationEffects_complete();
}
}
}
function commitBeforeMutationEffects_complete() {
while (nextEffect !== null) {
const fiber = nextEffect;
try {
commitBeforeMutationEffectsOnFiber(fiber);
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}
const sibling = fiber.sibling;
if (sibling !== null) {
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case ClassComponent: {
if ((flags & Snapshot) !== NoFlags) {
if (current !== null) {
// 调用 getSnapshotBeforeUpdate
const instance = finishedWork.stateNode;
const snapshot = instance.getSnapshotBeforeUpdate(
prevProps,
prevState,
);
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
}
return;
}
// ... 其他类型
}
}
7.5 Mutation 阶段(DOM 操作)
文件位置:packages/react-reconciler/src/ReactFiberCommitWork.js
export function commitMutationEffects(
root: FiberRoot,
finishedWork: Fiber,
committedLanes: Lanes,
): void {
inProgressLanes = committedLanes;
inProgressRoot = root;
commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
inProgressLanes = null;
inProgressRoot = null;
}
function commitMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
lanes: Lanes,
): void {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
console.log('Mutation:', getComponentNameFromFiber(finishedWork), flags);
// ========== 处理不同类型的副作用 ==========
// 处理 Ref
if (flags & Ref) {
if (current !== null) {
commitDetachRef(current);
}
}
// 处理 ContentReset(文本内容重置)
if (flags & ContentReset) {
commitResetTextContent(finishedWork);
}
// 处理 Update(更新 DOM 属性)
if (flags & Update) {
const instance: Instance = finishedWork.stateNode;
if (instance != null) {
const newProps = finishedWork.memoizedProps;
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
const updatePayload = finishedWork.updateQueue;
finishedWork.updateQueue = null;
if (updatePayload !== null) {
// 提交 DOM 更新
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
console.log('更新 DOM 属性:', type, updatePayload);
}
}
}
// 处理 Deletion(删除节点)
if (flags & ChildDeletion) {
const deletions = finishedWork.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
commitDeletion(root, childToDelete, finishedWork);
console.log('删除节点:', getComponentNameFromFiber(childToDelete));
}
}
}
// 处理 Placement(插入节点)
if (flags & Placement) {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
console.log('插入节点:', getComponentNameFromFiber(finishedWork));
}
// 递归处理子节点
const child = finishedWork.child;
if (child !== null) {
commitMutationEffectsOnFiber(child, root, lanes);
}
// 处理兄弟节点
const sibling = finishedWork.sibling;
if (sibling !== null) {
commitMutationEffectsOnFiber(sibling, root, lanes);
}
}
commitUpdate:更新 DOM 属性
// 文件位置:react-dom-bindings/src/client/ReactDOMComponent.js
export function commitUpdate(
domElement: Instance,
updatePayload: Array<mixed>,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
// updatePayload 格式:[key1, value1, key2, value2, ...]
// 例如:['className', 'active', 'style', { color: 'red' }]
updateProperties(domElement, updatePayload, type, oldProps, newProps);
// 更新 Fiber 引用
updateFiberProps(domElement, newProps);
}
function updateProperties(
domElement: Element,
updatePayload: Array<any>,
tag: string,
lastRawProps: Object,
nextRawProps: Object,
): void {
// 遍历 updatePayload,更新属性
for (let i = 0; i < updatePayload.length; i += 2) {
const propKey = updatePayload[i];
const propValue = updatePayload[i + 1];
if (propKey === 'style') {
// 更新样式
setValueForStyles(domElement, propValue);
} else if (propKey === 'children') {
// 更新文本内容
setTextContent(domElement, propValue);
} else {
// 更新其他属性
setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
}
console.log('更新属性:', propKey, propValue);
}
}
commitPlacement:插入 DOM 节点
function commitPlacement(finishedWork: Fiber): void {
// 步骤1:找到父 DOM 节点
const parentFiber = getHostParentFiber(finishedWork);
let parent;
switch (parentFiber.tag) {
case HostComponent:
parent = parentFiber.stateNode;
break;
case HostRoot:
parent = parentFiber.stateNode.containerInfo;
break;
// ...
}
// 步骤2:找到插入位置(before 节点)
const before = getHostSibling(finishedWork);
// 步骤3:插入节点
insertOrAppendPlacementNode(finishedWork, before, parent);
}
function insertOrAppendPlacementNode(
node: Fiber,
before: Instance | null,
parent: Instance,
): void {
const { tag } = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost) {
const stateNode = node.stateNode;
if (before) {
// 插入到 before 之前
insertBefore(parent, stateNode, before);
console.log('insertBefore:', parent, stateNode, before);
} else {
// 追加到末尾
appendChild(parent, stateNode);
console.log('appendChild:', parent, stateNode);
}
} else {
// 不是 Host 节点,继续查找子节点
const child = node.child;
if (child !== null) {
insertOrAppendPlacementNode(child, before, parent);
let sibling = child.sibling;
while (sibling !== null) {
insertOrAppendPlacementNode(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}
7.6 Layout 阶段
export function commitLayoutEffects(
finishedWork: Fiber,
root: FiberRoot,
committedLanes: Lanes,
): void {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = finishedWork;
commitLayoutEffects_begin(finishedWork, root, committedLanes);
inProgressLanes = null;
inProgressRoot = null;
}
function commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes,
): void {
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
// 执行 useLayoutEffect
if ((flags & (Update | Callback)) !== NoFlags) {
commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);
}
break;
}
case ClassComponent: {
const instance = finishedWork.stateNode;
if (flags & Update) {
if (current === null) {
// 首次渲染:调用 componentDidMount
instance.componentDidMount();
} else {
// 更新:调用 componentDidUpdate
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
}
}
// 执行 setState 的回调
const updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break;
}
case HostComponent: {
// 更新 ref
if (flags & Ref) {
commitAttachRef(finishedWork);
}
break;
}
// ... 其他类型
}
}
// 执行 useLayoutEffect
function commitHookLayoutEffects(finishedWork: Fiber, hookFlags: HookFlags) {
try {
commitHookEffectListMount(hookFlags, finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
const updateQueue = finishedWork.updateQueue;
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
// 执行 effect 函数
const create = effect.create;
const inst = effect.inst;
const destroy = create();
inst.destroy = destroy; // 保存清理函数
console.log('执行 useLayoutEffect:', create);
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
8. 批量更新机制
此时,我们来思考一个问题,如果我多次调用setState,会触发多次渲染吗?答案很明显是不会的,正常情况下,只会触发一次渲染,那么这是为什么呢。那么我们就需要了解下批量更新机制了。
8.1 什么是批量更新
批量更新:多次 setState 调用,只触发一次渲染。
function handleClick() {
setCount(count + 1); // 不会立即渲染
setName('React'); // 不会立即渲染
setLoading(false); // 不会立即渲染
// 三次 setState 只会触发一次渲染
}
每次 setState 都会创建 Update 对象并调用 Scheduler 吗?
回答:
- 每次
setState确实会创建一个 Update 对象 - 但不是每次都会立即调用 Scheduler,React 有**批量更新(Batch Update)**机制
完整流程图
用户代码: setState(newValue)
↓
dispatchSetState (创建 Update 对象)
↓
const update = {
lane, // 优先级
action: newValue, // 新状态值
next: null // 指向下一个 Update(循环链表)
}
↓
加入 Hook.queue.pending (循环链表)
↓
scheduleUpdateOnFiber (调度更新)
↓
【批量更新检查】
- 在事件处理函数中? → 批量处理,延迟调用 Scheduler
- 其他情况? → 立即调用 Scheduler
↓
ensureRootIsScheduled
↓
Scheduler.scheduleCallback (只调用一次!)
示例 1:单次 setState
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(1); // ← 只有这一次 setState
};
return <button onClick={handleClick}>{count}</button>;
}
执行流程:
// 1. 用户点击按钮,调用 handleClick
handleClick()
// 2. 执行 setCount(1)
dispatchSetState(fiber, queue, 1) {
// 创建 Update 对象
const update = {
lane: SyncLane,
action: 1,
next: null
};
// 加入 Hook 的更新队列(循环链表)
const pending = queue.pending;
if (pending === null) {
update.next = update; // 指向自己,形成循环
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
// 3. 调度更新
scheduleUpdateOnFiber(fiber.alternate, fiber, SyncLane);
}
// 4. 调用 Scheduler
ensureRootIsScheduled(root) {
Scheduler.scheduleCallback(
ImmediatePriority,
performSyncWorkOnRoot.bind(null, root)
);
}
结果:
- 创建 1 个 Update 对象
- 调用 1 次 Scheduler
示例 2:多次 setState(批量更新)
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(1); // ← 第 1 次
setCount(2); // ← 第 2 次
setCount(3); // ← 第 3 次
};
return <button onClick={handleClick}>{count}</button>;
}
执行流程:
// React 18+ 事件处理中的批量更新
handleClick() {
// React 内部设置批量更新标志
executionContext |= BatchedContext;
// 第 1 次 setCount(1)
dispatchSetState(..., 1) {
const update1 = { lane: SyncLane, action: 1, next: null };
update1.next = update1; // 循环链表:update1 → update1
queue.pending = update1;
scheduleUpdateOnFiber(...); // ← 检测到批量更新,暂不调用 Scheduler
}
// 第 2 次 setCount(2)
dispatchSetState(..., 2) {
const update2 = { lane: SyncLane, action: 2, next: null };
update2.next = update1.next; // update2 → update1
update1.next = update2; // update1 → update2
queue.pending = update2; // 循环链表:update2 → update1 → update2
scheduleUpdateOnFiber(...); // ← 检测到批量更新,暂不调用 Scheduler
}
// 第 3 次 setCount(3)
dispatchSetState(..., 3) {
const update3 = { lane: SyncLane, action: 3, next: null };
update3.next = update2.next; // update3 → update1
update2.next = update3; // update2 → update3
queue.pending = update3; // 循环链表:update3 → update2 → update1 → update3
scheduleUpdateOnFiber(...); // ← 检测到批量更新,暂不调用 Scheduler
}
// 事件处理函数执行完毕
executionContext &= ~BatchedContext; // 清除批量更新标志
// 现在才调用 Scheduler(只调用一次!)
ensureRootIsScheduled(root) {
Scheduler.scheduleCallback(
ImmediatePriority,
performSyncWorkOnRoot.bind(null, root)
);
}
}
结果:
- 创建 3 个 Update 对象(形成循环链表)
- 调用 1 次 Scheduler(批量更新)
Update 对象与 Fiber 的关系
FiberRootNode (根节点)
↓
FiberNode (如 App 组件的 Fiber)
↓
memoizedState → Hook 链表 (每个 Hook 一个节点)
↓
Hook {
memoizedState: 0, // 当前状态值
baseState: 0, // 基础状态
queue: UpdateQueue { // 更新队列 ★★★
pending: Update, // 待处理的 Update 循环链表
dispatch: dispatchSetState
},
next: Hook // 下一个 Hook
}
↓
UpdateQueue.pending → Update 循环链表
↓
Update {
lane: SyncLane, // 优先级
action: 3, // 新状态值或更新函数
next: Update // 下一个 Update(循环链表)
}
关键关系:
- Fiber.memoizedState → Hook 链表(每个 useState 对应一个 Hook)
- Hook.queue → UpdateQueue(存储所有 setState 创建的 Update)
- UpdateQueue.pending → Update 循环链表(用
next指针连接) - 一个 Fiber 可以有多个 Hook(多个 useState)
- 一个 Hook 可以有多个 Update(多次 setState)
批量更新的源码实现
文件位置:packages/react-reconciler/src/ReactFiberHooks.js
function dispatchSetState(fiber, queue, action) {
// 1. 创建 Update 对象
const lane = requestUpdateLane(fiber);
const update = {
lane,
action,
hasEagerState: false,
eagerState: null,
next: null,
};
// 2. 加入循环链表
const pending = queue.pending;
if (pending === null) {
// 第一个 Update,指向自己形成循环
update.next = update;
} else {
// 插入链表:update → pending.next → ... → pending → update
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
// 3. 优化:提前计算状态(eager state)
const alternate = fiber.alternate;
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// 当前没有其他更新,可以立即计算新状态
const lastRenderedReducer = queue.lastRenderedReducer;
const currentState = queue.lastRenderedState;
const eagerState = lastRenderedReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
// 如果状态没变,不需要调度(优化!)
if (Object.is(eagerState, currentState)) {
return; // ← 直接返回,不调度更新
}
}
// 4. 调度更新
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane); // ← 这里会检查批量更新
}
}
批量更新检查:
// packages/react-reconciler/src/ReactFiberWorkLoop.js
export function scheduleUpdateOnFiber(root, fiber, lane) {
markRootUpdated(root, lane);
// 检查当前执行上下文
if ((executionContext & BatchedContext) !== NoContext) {
// 在批量更新上下文中,暂不调用 Scheduler
return;
}
// 否则立即调度
ensureRootIsScheduled(root);
}
批量更新总结
| 场景 | Update 对象数量 | Scheduler 调用次数 | 说明 |
|---|---|---|---|
| 单次 setState | 1 个 | 1 次 | 立即调度 |
| 事件处理中多次 setState | N 个 | 1 次 | 批量更新,只在事件结束后调用一次 |
| setTimeout 中多次 setState (React 17) | N 个 | N 次 | 每次都调度(无批量更新) |
| setTimeout 中多次 setState (React 18+) | N 个 | 1 次 | 自动批量更新(Automatic Batching) |
核心要点:
- 每次 setState 都创建 Update 对象
- 不是每次都调用 Scheduler,批量更新会合并多次 setState
- Update 对象存储在 Fiber → Hook → UpdateQueue.pending 循环链表中
- React 18 引入 Automatic Batching,所有场景都支持批量更新
8.3 React 18 的自动批处理
React 18 之前:
// 在事件处理中:批处理 ✓
handleClick() {
setCount(1);
setName('A');
// 只渲染一次
}
// 在 setTimeout 中:不批处理 ✗
setTimeout(() => {
setCount(1); // 渲染一次
setName('A'); // 又渲染一次
}, 1000);
// 在 Promise 中:不批处理 ✗
fetch('/api').then(() => {
setCount(1); // 渲染一次
setName('A'); // 又渲染一次
});
React 18:所有情况都自动批处理 ✓
// 事件处理:批处理
handleClick() {
setCount(1);
setName('A');
}
// setTimeout:也批处理
setTimeout(() => {
setCount(1);
setName('A');
}, 1000);
// Promise:也批处理
fetch('/api').then(() => {
setCount(1);
setName('A');
});
8.4 批处理的实现原理
关键变量:executionContext
// packages/react-reconciler/src/ReactFiberWorkLoop.js
let executionContext: ExecutionContext = NoContext;
const NoContext = 0b000;
const BatchedContext = 0b001; // 批处理上下文
const RenderContext = 0b010; // 渲染上下文
const CommitContext = 0b100; // 提交上下文
事件处理中的批处理:
// 文件位置:react-dom-bindings/src/events/ReactDOMUpdateBatching.js
export function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
const prevExecutionContext = executionContext;
// 设置批处理上下文
executionContext |= BatchedContext;
try {
// 执行事件处理函数
return fn(a);
} finally {
// 恢复上下文
executionContext = prevExecutionContext;
// 如果没有其他上下文,刷新同步工作
if (executionContext === NoContext) {
flushSyncWorkOnAllRoots();
}
}
}
在批处理上下文中调用 setState:
// ensureRootIsScheduled 中的逻辑
function ensureRootIsScheduled(root: FiberRoot): void {
// ...
if (executionContext & (RenderContext | CommitContext)) {
// 在渲染或提交阶段,不立即调度(关键)
return;
}
// 调度更新
scheduleCallback(...);
}
8.5 flushSync:同步更新
如果需要立即更新(不批处理):
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCount(count + 1); // 立即渲染
});
console.log(ref.current.value); // 可以读取到最新的 DOM
flushSync(() => {
setName('React'); // 又立即渲染
});
}
flushSync 实现:
export function flushSync<R>(fn: () => R): R {
const prevExecutionContext = executionContext;
// 添加 BatchedContext,防止嵌套 flushSync
executionContext |= BatchedContext;
// 设置同步优先级
const prevUpdatePriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority);
try {
// 执行函数
return fn();
} finally {
// 恢复优先级
setCurrentUpdatePriority(prevUpdatePriority);
executionContext = prevExecutionContext;
// 立即刷新所有同步工作
if (executionContext === NoContext) {
flushSyncWorkOnAllRoots();
}
}
}
9. 完整时序图
9.1 从点击到 DOM 更新的完整流程
用户点击"电子产品"按钮
↓
onClick('electronics')
↓
handleCategoryChange('electronics')
↓
setFilterCategory('electronics')
↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 1: setState 调用阶段(同步)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↓
dispatchSetState(fiber, queue, 'electronics')
├─ 计算优先级:lane = DefaultLane
├─ 创建 Update 对象
├─ 预计算新状态:'electronics'
└─ dispatchSetStateInternal
├─ enqueueConcurrentHookUpdate
│ └─ 将 Update 加入 queue.pending(环形链表)
└─ scheduleUpdateOnFiber
├─ markRootUpdated(root, DefaultLane)
│ └─ root.pendingLanes |= DefaultLane
└─ ensureRootIsScheduled(root)
├─ getNextLanes → DefaultLane
├─ lanesToSchedulerPriority → NormalPriority
└─ scheduleCallback(
NormalPriority,
performConcurrentWorkOnRoot
)
↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 2: Scheduler 调度阶段(异步)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↓
【等待浏览器空闲或高优先级任务】
↓
Scheduler 执行任务:performConcurrentWorkOnRoot(root)
↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 3: Render 阶段(可中断)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↓
renderRootConcurrent(root, DefaultLane)
├─ prepareFreshStack(root, DefaultLane)
│ ├─ 创建 workInProgress 树
│ └─ workInProgress = HostRootFiber'
└─ workLoopConcurrent()
│
↓ while (workInProgress !== null && !shouldYield())
│
performUnitOfWork(workInProgress)
│
├─ beginWork(current, workInProgress, DefaultLane)
│ │
│ ├─ HostRoot → updateHostRoot
│ │ ├─ 处理 updateQueue
│ │ ├─ reconcileChildren
│ │ └─ return child (App Fiber)
│ │
│ ├─ App → updateFunctionComponent
│ │ ├─ renderWithHooks(App)
│ │ ├─ 执行 App 函数
│ │ ├─ reconcileChildren
│ │ └─ return child (Header Fiber)
│ │
│ ├─ Header → bailout (没有变化,跳过)
│ │
│ ├─ ProductList → updateFunctionComponent
│ │ ├─ renderWithHooks(ProductList)
│ │ │ ├─ 设置 Dispatcher = HooksDispatcherOnUpdate
│ │ │ ├─ ProductList(props)
│ │ │ │ ├─ useState([]) → Hook1
│ │ │ │ ├─ useState('all') → Hook2 ★
│ │ │ │ │ ├─ updateState
│ │ │ │ │ ├─ updateReducer
│ │ │ │ │ ├─ 处理 queue.pending
│ │ │ │ │ │ └─ Update { action: 'electronics' }
│ │ │ │ │ ├─ 计算新状态
│ │ │ │ │ │ └─ basicStateReducer('all', 'electronics')
│ │ │ │ │ │ → 'electronics'
│ │ │ │ │ └─ return ['electronics', setFilterCategory]
│ │ │ │ ├─ useState('price') → Hook3
│ │ │ │ ├─ useState(false) → Hook4
│ │ │ │ └─ useEffect(fetchProducts, [filterCategory, sortBy]) → Hook5
│ │ │ │ ├─ updateEffect
│ │ │ │ ├─ 比较依赖:['electronics', 'price'] vs ['all', 'price']
│ │ │ │ ├─ filterCategory 变化!
│ │ │ │ └─ 标记 HookHasEffect
│ │ │ └─ return JSX (CategoryFilter, SortOptions, ProductGrid)
│ │ └─ reconcileChildren
│ │ ├─ CategoryFilter → 新 props
│ │ ├─ SortOptions → 无变化
│ │ └─ ProductGrid → 新 props
│ │
│ ├─ CategoryFilter → updateFunctionComponent
│ │ ├─ 检测到 props 变化 (selected: 'all' → 'electronics')
│ │ ├─ renderWithHooks
│ │ └─ reconcileChildren → FilterButton
│ │ └─ className: '' → 'active' (标记 Update)
│ │
│ └─ ... (其他组件)
│
└─ completeWork(workInProgress)
├─ HostComponent (FilterButton)
│ ├─ 对比 props
│ ├─ 计算 updatePayload
│ │ └─ ['className', 'active']
│ └─ workInProgress.updateQueue = updatePayload
│
└─ 标记 flags
└─ workInProgress.flags |= Update
↓
【所有 Fiber 节点处理完成】
workInProgress = null
↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 4: Commit 阶段(同步,不可中断)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↓
commitRoot(root)
↓
commitRootImpl(root)
│
├─ Before Mutation 阶段
│ ├─ commitBeforeMutationEffects
│ ├─ 调用 getSnapshotBeforeUpdate(类组件)
│ └─ 调度 useEffect(异步执行)
│
├─ Mutation 阶段 DOM 更新发生
│ ├─ commitMutationEffects
│ ├─ 遍历 Fiber 树
│ └─ FilterButton Fiber
│ ├─ flags & Update
│ ├─ commitUpdate(domElement, ['className', 'active'])
│ └─ updateProperties
│ └─ domElement.className = 'active'
│ ↓
│ 【DOM 更新完成】
│
├─ 切换 current 树
│ └─ root.current = finishedWork
│
└─ Layout 阶段
├─ commitLayoutEffects
├─ 执行 useLayoutEffect
├─ 调用 componentDidMount/Update
└─ 更新 ref.current
↓
【Commit 完成】
↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 5: 浏览器渲染
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↓
浏览器重绘
↓
【用户看到界面更新】✓
按钮的 className 从 '' 变为 'active'
↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 6: 异步执行 useEffect
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↓
【浏览器空闲时】
↓
flushPassiveEffects()
├─ commitPassiveUnmountEffects
│ └─ 执行上一次 effect 的清理函数(如果有)
└─ commitPassiveMountEffects
└─ ProductList 的 useEffect
├─ 检查 tag & HookHasEffect
├─ 执行 effect 函数
│ └─ fetchProducts()
│ ├─ setLoading(true) → 触发新的更新
│ ├─ await api.getProducts(...)
│ ├─ setProducts(data) → 触发新的更新
│ └─ setLoading(false) → 触发新的更新
└─ 保存 destroy 函数
↓
【新的更新循环开始】
从 Phase 1 重新开始...
9.2 关键时间点
| 时间点 | 事件 | 用户可见性 | 耗时 |
|---|---|---|---|
| T0 | 用户点击按钮 | - | 0ms |
| T1 | setState 调用完成 | - | <1ms |
| T2 | Scheduler 调度完成 | - | <1ms |
| T3 | Render 阶段开始 | - | 0-5ms |
| T4 | Render 阶段完成 | - | (可能被中断) |
| T5 | Commit - Mutation | 界面更新 ✓ | 1-3ms |
| T6 | Commit - Layout | - | <1ms |
| T7 | 浏览器重绘 | 用户看到变化 | ~16ms |
| T8 | useEffect 执行 | - | (异步) |
总时间(T0 → T7):通常 10-30ms(用户无感知)
总结
setState 完整流程总结
- 调用 setState:创建 Update 对象,加入队列,调度更新
- 计算优先级:使用 Lane 模型,根据事件类型确定优先级
- Scheduler 调度:根据优先级,将渲染任务加入任务队列
- Render 阶段:
- beginWork:处理组件,执行 Hooks,计算新状态
- completeWork:创建/更新 DOM 节点(不插入页面)
- 使用 Diff 算法最小化 DOM 操作
- Commit 阶段:
- Before Mutation:执行 getSnapshotBeforeUpdate
- Mutation:真正的 DOM 更新(用户看到变化)
- Layout:执行 useLayoutEffect、componentDidMount/Update
- 异步执行 useEffect:在浏览器空闲时执行
关键优化点
- 批量更新:多次 setState 只触发一次渲染
- 预计算优化:如果新旧状态相同,跳过更新
- Bailout 机制:组件 props 未变化,跳过渲染
- 时间切片:Render 阶段可中断,不阻塞用户交互
- 优先级调度:高优先级任务优先处理
更多推荐



所有评论(0)