prepareFreshStack 源码解析

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.timeoutHandleSuspense 组件设置的超时定时器(如在 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 一致的状态,清理 effecthooksstateprops 变更,让这个节点回到渲染前的状态

设计思想

  • 可中断渲染的安全保障:中断不代表崩溃,必须撤销所有半完成的工作 ,内存树(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 复制除调度元数据外的所有字段,清空 flagsflags = NoFlagssubtreeFlags = NoFlagsdeletions = 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 展开。决定了 beginWorkcurrent.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 标记是否在本次渲染中 attachPromiseping 回调。在 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 标记哪些 lanePromiseresolve,渲染结束后需要重新调度这些 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 累积的更新转移到 fiberupdateQueue.shared.pending 中,保证不丢失任何更新。在 prepareFreshStack 中调用它意味着在新渲染开始前,清理上一次渲染可能残留的未处理交插更新

设计思想

并发安全:渲染过程中产生的新状态不会丢失,保证最终一致性


三、设计思想

  • 幂等清理 :每次渲染前取消所有旧异步状态(定时器、pending commitpending effects),确保新渲染不受干扰
  • 双缓冲createWorkInProgress 不深度克隆,只复制引用,flags 清空,return 留空
  • 状态机 :16 个全局变量规范渲染流程,workInProgressRootExitStatusRootInProgress 开始,逐步演变
  • 纠缠getEntangledLanes 保证逻辑批次的更新同时出现,防止 UI 撕裂
  • 渲染前入队清理finishQueueingConcurrentUpdates 处理完上一轮的残留交插更新后再开始新渲染
  • 并发安全:取消旧任务,不提交旧渲染,新任务覆盖旧任务
  • 预渲染优化workInProgressRootIsPrerendering 只在预渲染时设为 true,影响 work loop 的行为决策
相关推荐
Aolith2 小时前
从 Pinia 到 Zustand:我在 React 里复刻了一套用户状态管理
前端·react.js·typescript
右耳朵猫AI2 小时前
React周刊2026W22 | React 13周年、React Router 7.16.0、Spoiled 0.5
前端·react.js·前端框架
右耳朵猫AI2 小时前
前端周刊2026W22 | React 13周年、TanStack Router、Deno 2.8、Node.js 26、npm 分阶段发布
前端·react.js·node.js
产品研究员18 小时前
AI生成可用的React交互代码实测:Lovable vs Stitch vs Paico
前端·react.js·aigc
不爱吃糖的程序媛1 天前
React Native 三方库 react-native-version-number 鸿蒙适配实战:从零到版本信息展示
react native·react.js·harmonyos
Dragon Wu1 天前
React Native 配置自定义字体
react native·react.js
英俊潇洒美少年1 天前
前端全量资源预加载优化指南(React内置API + Vue实现 + prerender/prefetch深度对比)
前端·react.js·前端框架
vim怎么退出1 天前
Dive into React——调度/并发
前端·react.js·源码阅读