React 每次全新渲染 的起点函数,彻底清空上一次渲染的所有残留状态,创建一个干净、空白的渲染环境,准备从头开始构建新 fiber 树
核心使命:
- 重置所有全局 / 模块级变量
- 创建新的
workInProgress树 - 隔离本次渲染,不污染、不残留、不干扰
- 让渲染从干净空白状态开始
一、完整调用链路
scss
prepareFreshStack
│
├─取消旧定时器
│
├─resetWorkInProgressStack()
│
├─createWorkInProgress(root.current, null)
│
├─重置 16 个状态变量
│
├─getEntangledLanes(root, lanes)
│
├─finishQueueingConcurrentUpdates()
│
└─return rootWorkInProgress
二、代码解析
步骤一、取消上一次渲染的定时器
javascript
const timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
root.timeoutHandle = noTimeout;
cancelTimeout(timeoutHandle); // 取消旧的 Suspense fallback 定时器
}
const cancelPendingCommit = root.cancelPendingCommit;
if (cancelPendingCommit !== null) {
root.cancelPendingCommit = null;
cancelPendingCommit(); // 取消挂起的 commit(例如 Offscreen 的保留)
}
作用
root.timeoutHandle:Suspense组件设置的超时定时器(如在SuspendedWithDelay状态下设的 500ms fallback 延迟)。新渲染开始 → 旧定时器无意义 → 取消root.cancelPendingCommit:某个待提交流程的取消回调(如Offscreen边界的保留commit)。新渲染开始 → 旧提交无效 → 取消
设计思想
幂等清理 :React 的渲染可以随时被更高优先级的更新中断。当一个中断发生时,旧渲染残留的所有异步副作用 (定时器、pending commit)必须取消,否则新渲染提交后旧的定时器还会触发 fallback,造成视觉闪烁。
步骤二、重置副作用队列
javascript
pendingEffectsLanes = NoLanes;
作用
pendingEffectsLanes 在 commit 阶段收集需要执行副作用的 lane。新渲染时清空所有等待执行的副作用(Effect),全新渲染不携带旧的 Effect
设计思想
避免旧渲染的副作用污染新渲染 :此变量在 commitRoot 中检查,如果不清零,旧渲染残留的 lane 可能导致副作用被错误地再次执行。
步骤三、丢弃旧 WiP 树 + 创建新 WiP 树
javascript
// ① 清空 unwind 栈
resetWorkInProgressStack();
// ② 设置当前 root
workInProgressRoot = root;
// ③ 克隆 current → WiP
const rootWorkInProgress = createWorkInProgress(root.current, null);
// ④ 设置遍历指针
workInProgress = rootWorkInProgress;
作用
- 清空
Suspense unwind时构建的栈帧回溯队列 ,重置所有fiber节点的挂起、中断状态。当React遇到Suspense并决定unwind时,它会记录已处理过的fiber,以便恢复时可回到正确位置。新渲染开始时,这些记录已无意义。 - 创建新的
WIP树
设计思想
双缓冲机制:新渲染永远在内存中进行,不影响当前 UI,渲染完成后一次性替换
resetWorkInProgressStack
当渲染被中断、取消、重置时,安全地回退所有未完成的 fiber 节点,清理所有中间状态,最后把 workInProgress 设为 null,彻底废弃当前渲染栈
javascript
function resetWorkInProgressStack() {
// ① 没有需要渲染 / 回退的 fiber,直接结束,避免空操作
if (workInProgress === null) return;
// ② 判断中断来源 → 决定从哪里开始回退
let interruptedWork;
// 情况 A:普通中断(渲染被打断,但没有挂起、没有错误、没有 Promise,只是中途取消)
if (workInProgressSuspendedReason === NotSuspended) {
// 渲染还没真正执行,只是准备中
// 不需要回退当前节点,直接从父节点开始回退整个栈
interruptedWork = workInProgress.return;
} else {
// 情况 B:渲染处于挂起状态(Suspense / 错误 / Promise)
// 重置挂起循环状态,清理挂起标记、重试状态、Promise 监听
resetSuspendedWorkLoopOnUnwind(workInProgress);
// 从当前挂起的节点自身开始回退
interruptedWork = workInProgress;
}
// 向上遍历所有父节点,逐个回退
while (interruptedWork !== null) {
// alternate 指向 current 树(页面上真实显示的 fiber)
// interruptedWork 是 workInProgress 树(内存中渲染中的 fiber)
const current = interruptedWork.alternate;
// 撤销这个节点的所有渲染工作,当作没渲染过
unwindInterruptedWork(
current,
interruptedWork,
workInProgressRootRenderLanes,
);
// 回退完当前节点,继续向上回退父节点,直到根节点为止,保证整棵树都被安全回退
interruptedWork = interruptedWork.return;
}
// 标记渲染栈彻底清空,全新渲染可以安全开始
workInProgress = null;
}
作用
撤销当前 fiber 节点在内存中做的所有修改 ,恢复成和页面 UI 一致的状态,清理 effect、hooks、state、props 变更,让这个节点回到渲染前的状态
设计思想
- 可中断渲染的安全保障:中断不代表崩溃,必须撤销所有半完成的工作 ,内存树(
workInProgress)可以随时丢弃 - 分层回退,绝不污染 UI:只回退内存
fiber树 ,绝不操作DOM,保证 UI 稳定、不闪烁、不混乱 - 区分 "普通中断" 与 "挂起中断" :普通中断:轻量回退;挂起中断:必须先重置挂起状态,再回退→ 逻辑精细化,性能最优
- 渲染幂等性、确定性:无论中断多少次,回退后状态永远一致,可随时重新开始渲染
createWorkInProgress
typescript
function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {
// 第一次渲染(current.alternate === null)→ 创建新 fiber
workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
// 复用已有的 alternate
workInProgress.pendingProps = pendingProps;
workInProgress.type = current.type;
workInProgress.flags = NoFlags; // ← 关键:清空 flags
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
}
// 从 current 复制除 flags/alternate 外所有属性
workInProgress.flags = current.flags & StaticMask;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
workInProgress.refCleanup = current.refCleanup;
// lanes 相关
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes; // ← 保留 lanes
// 内部指针清空
workInProgress.return = null; // ← 递归时由 beginWork 设置
return workInProgress;
}
作用:
- 首次渲染(
root.current.alternate === null):createFiber分配新fiber对象,形成current.alternate ↔ workInProgress双向链接 - 非首次:复用已有的
alternate,从 current 复制除调度元数据外的所有字段,清空 flags (flags = NoFlags、subtreeFlags = NoFlags、deletions = null)
| 字段 | 从 current 复制 | 含义 |
|---|---|---|
memoizedProps |
✅ | 上一次提交的 props |
memoizedState |
✅ | 上一次提交的 state |
updateQueue |
✅ | 共享的更新队列引用 |
child / sibling |
✅ | fiber 树结构复制 |
lanes / childLanes |
✅ | 保留待处理优先级 |
flags |
❌ 清空 | 新渲染需要重新计算 |
subtreeFlags |
❌ 清空 | 新渲染需要重新计算 |
deletions |
❌ 清空 | 新渲染需要重新计算 |
return |
❌ 设为 null | beginWork 遍历时重新设置 |
⭐ 设计思想:双缓冲
- 零拷贝语义 :
WiP树不是深度克隆------child/sibling指针直接复制current树的结构。只有发生变化的fiber才会在beginWork中创建新fiber或修改属性 flags隔离 :current树的flags是上一次commit的结果(已消耗),WiP树必须从零计算本次的flags。不清空会导致重复或错误的副作用return指针动态构建 :return不复制,因为在beginWork → completeUnitOfWork的递归遍历中会根据实际遍历路径重新设置,避免引用旧的已删除fiber
步骤四、重置全局渲染状态变量
javascript
workInProgressRootRenderLanes = lanes; // 当前渲染的 lane
workInProgressSuspendedReason = NotSuspended; // 挂起原因重置
workInProgressThrownValue = null; // throw 值清空
workInProgressRootDidSkipSuspendedSiblings = false; // 跳过兄弟标记
workInProgressRootIsPrerendering = checkIfRootIsPrerendering(root, lanes); // 预渲染判定
workInProgressRootDidAttachPingListener = false; // ping 监听器标记
workInProgressRootExitStatus = RootInProgress; // 出口状态 ← 最重要!
workInProgressRootSkippedLanes = NoLanes; // 跳过的 lane
workInProgressRootInterleavedUpdatedLanes = NoLanes; // 交插更新的 lane
workInProgressRootRenderPhaseUpdatedLanes = NoLanes; // 渲染阶段更新的 lane
workInProgressRootPingedLanes = NoLanes; // ping 过的 lane
workInProgressDeferredLane = NoLane; // 延迟 lane
workInProgressSuspendedRetryLanes = NoLanes; // 挂起重试 lane
workInProgressRootConcurrentErrors = null; // 并发错误
workInProgressRootRecoverableErrors = null; // 可恢复错误
workInProgressRootDidIncludeRecursiveRenderUpdate = false; // 递归更新标记
每个变量的作用与设计意义
| 变量 | 类型 | 默认值 | ⭐ 设计意义 |
|---|---|---|---|
workInProgressRootRenderLanes |
Lanes | lanes (入参) |
本次渲染围绕哪些 lane 展开。决定了 beginWork 中 current.lanes 的判断、renderLanes 向下传递 |
workInProgressSuspendedReason |
SuspendedReason | NotSuspended |
引导 work loop 的挂起处理分支------renderRootSync vs renderRootConcurrent 都依赖此变量决定是否 unwind / break / replay |
workInProgressThrownValue |
any | null |
handleThrow 捕获的 throw 值(通常是 Promise)。重放时取回 |
workInProgressRootDidSkipSuspendedSiblings |
boolean | false |
优化标记:当 Suspense 边界因挂起跳过兄弟节点时,不能再做 bailout 优化 |
workInProgressRootIsPrerendering |
boolean | 动态计算 | 预渲染时 work loop 行为不同(不做真正的渲染,只做"热身") |
workInProgressRootDidAttachPingListener |
boolean | false |
标记是否在本次渲染中 attach 了 Promise 的 ping 回调。在 finishConcurrentRender 中决定是否要退出而不调度 |
workInProgressRootExitStatus |
RootExitStatus | RootInProgress |
⭐ 核心设计 :RootInProgress = 0,渲染未完成。renderRootConcurrent 经常返回此值表示"中断了,还没完"。完成时设为 RootCompleted |
workInProgressRootSkippedLanes |
Lanes | NoLanes |
在 bailout 时跳过的 lane。这些 lane 不会在当前渲染中处理,需重新调度 |
workInProgressRootInterleavedUpdatedLanes |
Lanes | NoLanes |
渲染过程中从外部 (如事件回调)入队的交插更新。React 会在完成当前渲染后重新调度 |
workInProgressRootRenderPhaseUpdatedLanes |
Lanes | NoLanes |
渲染阶段内部通过 setState 触发的更新。此时不能重新调度(会导致死循环),而是在当前渲染中处理 |
workInProgressRootPingedLanes |
Lanes | NoLanes |
标记哪些 lane 的 Promise 已 resolve,渲染结束后需要重新调度这些 lane |
workInProgressDeferredLane |
NoLane | NoLane |
用于延迟过渡更新的 lane。当更新被推迟到下一个过渡时使用 |
workInProgressSuspendedRetryLanes |
Lanes | NoLanes |
挂起重试的 lane 集合 |
workInProgressRootConcurrentErrors |
Array | null |
并发渲染中的错误(非致命,可 recover) |
workInProgressRootRecoverableErrors |
Array | null |
可恢复的错误(如 Suspense) |
workInProgressRootDidIncludeRecursiveRenderUpdate |
boolean | false |
检测递归渲染(在渲染阶段触发的 setState 可能导致无限递归) |
⭐ 设计思想:状态机
这 16 个全局变量构成一个规范的有限状态机,状态变化路径如下:
javascript
prepareFreshStack → 所有变量初始化为默认值
│
▼
workLoop(Sync/Concurrent) 期间动态变化
│
├─ SuspendedReason 从 NotSuspended → SuspendedOnData(组件 throw Promise)
├─ ExitStatus 从 RootInProgress → RootSuspended / RootCompleted
├─ SkippedLanes / PingedLanes / InterleavedUpdatedLanes 逐步累积
│
▼
完工 → finishConcurrentRender 读取这些变量决定下一步动作
│
├─ RootCompleted → commitRoot (提交)
├─ RootSuspended → 调度 fallback 延时 或 ping 后重新调度
└─ RootInProgress → 时间片用完,重新调度 continue
步骤五、计算纠缠 lanes
javascript
entangledRenderLanes = getEntangledLanes(root, lanes);
function getEntangledLanes(root: FiberRoot, renderLanes: Lanes): Lanes {
// 找出与 renderLanes 纠缠的 lane
// 纠缠 = 这些 lane 的更新必须一起提交,即使优先级不同
let entangledLanes = renderLanes;
// 检查 pendingLanes 中是否有与 renderLanes 纠缠的 lane
// 如果 renderLanes 包含某个 lane,且该 lane 与其他 lane 纠缠
// 则必须一同渲染
let allEntangled = NoLanes;
forEachLane(entangledLanes, lane => {
const entangled = root.entangledLanes & lane;
if (entangled !== NoLane) {
// 此 lane 有纠缠,找出所有相关 lane
const entangledLane = entangled & -entangled;
allEntangled |= entangledLane;
// 继续查找与纠缠 lane 再纠缠的 lane(传递闭包)
let nestedEntangled = root.entangledLanes & entangledLane;
while (nestedEntangled !== NoLane) {
const nestedLane = nestedEntangled & -nestedEntangled;
allEntangled |= nestedLane;
nestedEntangled &= ~nestedLane;
}
}
});
if (allEntangled !== NoLanes) {
entangledLanes |= allEntangled;
}
return entangledLanes;
}
作用
处理混合优先级渲染 :找出与当前渲染的 lanes 纠缠 的所有 lane。纠缠指的是一组更新必须在同一个渲染批次中完成,即使它们有不同的优先级。
⭐ 设计思想
防止撕裂(tearing) :没有纠缠机制时,如果用户在一个事件处理函数中调用了多个 setState,每个可能分配到不同的 lane,导致多次不完整的渲染。纠缠确保"同一个逻辑批次"的更新永远同时出现、同时提交,避免 UI 状态不一致,并发渲染必须支持多优先级混合
步骤六、收尾并发入队更新
javascript
finishQueueingConcurrentUpdates();
作用
finishQueueingConcurrentUpdates 将渲染期间通过 concurrentQueues 累积的更新转移到 fiber 的 updateQueue.shared.pending 中,保证不丢失任何更新。在 prepareFreshStack 中调用它意味着在新渲染开始前,清理上一次渲染可能残留的未处理交插更新。
设计思想
并发安全:渲染过程中产生的新状态不会丢失,保证最终一致性
三、设计思想
- 幂等清理 :每次渲染前取消所有旧异步状态(定时器、
pending commit、pending effects),确保新渲染不受干扰 - 双缓冲 :
createWorkInProgress不深度克隆,只复制引用,flags清空,return留空 - 状态机 :16 个全局变量规范渲染流程,
workInProgressRootExitStatus从RootInProgress开始,逐步演变 - 纠缠 :
getEntangledLanes保证逻辑批次的更新同时出现,防止 UI 撕裂 - 渲染前入队清理 :
finishQueueingConcurrentUpdates处理完上一轮的残留交插更新后再开始新渲染 - 并发安全:取消旧任务,不提交旧渲染,新任务覆盖旧任务
- 预渲染优化 :
workInProgressRootIsPrerendering只在预渲染时设为true,影响work loop的行为决策