prepareFreshStack 源码解析

0 阅读9分钟

React 每次全新渲染的起点函数,彻底清空上一次渲染的所有残留状态,创建一个干净、空白的渲染环境,准备从头开始构建新 fiber

核心使命

  • 重置所有全局 / 模块级变量
  • 创建新的 workInProgress
  • 隔离本次渲染,不污染、不残留、不干扰
  • 让渲染从干净空白状态开始

一、完整调用链路

prepareFreshStack
  │                                
  ├─取消旧定时器
  │                                
  ├─resetWorkInProgressStack()
  │                                 
  ├─createWorkInProgress(root.current, null)
  │                                 
  ├─重置 16 个状态变量
  │                                
  ├─getEntangledLanes(root, lanes)
  │                                 
  ├─finishQueueingConcurrentUpdates()
  │                                 
  └─return rootWorkInProgress

二、代码解析

步骤一、取消上一次渲染的定时器

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,造成视觉闪烁。


步骤二、重置副作用队列

pendingEffectsLanes = NoLanes;

作用

pendingEffectsLanes 在 commit 阶段收集需要执行副作用的 lane。新渲染时清空所有等待执行的副作用(Effect),全新渲染不携带旧的 Effect

设计思想

避免旧渲染的副作用污染新渲染:此变量在 commitRoot 中检查,如果不清零,旧渲染残留的 lane 可能导致副作用被错误地再次执行。


步骤三、丢弃旧 WiP 树 + 创建新 WiP 树

// ① 清空 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,彻底废弃当前渲染栈

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

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 / siblingfiber 树结构复制
lanes / childLanes保留待处理优先级
flags❌ 清空新渲染需要重新计算
subtreeFlags❌ 清空新渲染需要重新计算
deletions❌ 清空新渲染需要重新计算
return❌ 设为 nullbeginWork 遍历时重新设置

⭐ 设计思想:双缓冲

  • 零拷贝语义WiP 树不是深度克隆——child / sibling 指针直接复制 current 树的结构。只有发生变化的 fiber 才会在 beginWork 中创建新 fiber 或修改属性
  • flags 隔离current 树的 flags 是上一次 commit 的结果(已消耗),WiP 树必须从零计算本次的 flags。不清空会导致重复或错误的副作用
  • return 指针动态构建return 不复制,因为在 beginWork → completeUnitOfWork 的递归遍历中会根据实际遍历路径重新设置,避免引用旧的已删除 fiber

步骤四、重置全局渲染状态变量

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;           // 递归更新标记

每个变量的作用与设计意义

变量类型默认值⭐ 设计意义
workInProgressRootRenderLanesLaneslanes (入参)本次渲染围绕哪些 lane 展开。决定了 beginWorkcurrent.lanes 的判断、renderLanes 向下传递
workInProgressSuspendedReasonSuspendedReasonNotSuspended引导 work loop 的挂起处理分支——renderRootSync vs renderRootConcurrent 都依赖此变量决定是否 unwind / break / replay
workInProgressThrownValueanynullhandleThrow 捕获的 throw 值(通常是 Promise)。重放时取回
workInProgressRootDidSkipSuspendedSiblingsbooleanfalse优化标记:当 Suspense 边界因挂起跳过兄弟节点时,不能再做 bailout 优化
workInProgressRootIsPrerenderingboolean动态计算预渲染时 work loop 行为不同(不做真正的渲染,只做"热身")
workInProgressRootDidAttachPingListenerbooleanfalse标记是否在本次渲染中 attachPromiseping 回调。在 finishConcurrentRender 中决定是否要退出而不调度
workInProgressRootExitStatusRootExitStatusRootInProgress核心设计RootInProgress = 0,渲染未完成。renderRootConcurrent 经常返回此值表示"中断了,还没完"。完成时设为 RootCompleted
workInProgressRootSkippedLanesLanesNoLanesbailout 时跳过的 lane。这些 lane 不会在当前渲染中处理,需重新调度
workInProgressRootInterleavedUpdatedLanesLanesNoLanes渲染过程中从外部(如事件回调)入队的交插更新。React 会在完成当前渲染后重新调度
workInProgressRootRenderPhaseUpdatedLanesLanesNoLanes渲染阶段内部通过 setState 触发的更新。此时不能重新调度(会导致死循环),而是在当前渲染中处理
workInProgressRootPingedLanesLanesNoLanes标记哪些 lanePromiseresolve,渲染结束后需要重新调度这些 lane
workInProgressDeferredLaneNoLaneNoLane用于延迟过渡更新的 lane。当更新被推迟到下一个过渡时使用
workInProgressSuspendedRetryLanesLanesNoLanes挂起重试的 lane 集合
workInProgressRootConcurrentErrorsArraynull并发渲染中的错误(非致命,可 recover
workInProgressRootRecoverableErrorsArraynull可恢复的错误(如 Suspense
workInProgressRootDidIncludeRecursiveRenderUpdatebooleanfalse检测递归渲染(在渲染阶段触发的 setState 可能导致无限递归)

⭐ 设计思想:状态机

这 16 个全局变量构成一个规范的有限状态机,状态变化路径如下:

prepareFreshStack → 所有变量初始化为默认值
         │
         ▼
workLoop(Sync/Concurrent) 期间动态变化
         │
         ├─ SuspendedReasonNotSuspendedSuspendedOnData(组件 throw Promise)
         ├─ ExitStatusRootInProgressRootSuspended / RootCompleted
         ├─ SkippedLanes / PingedLanes / InterleavedUpdatedLanes 逐步累积
         │
         ▼
完工 → finishConcurrentRender 读取这些变量决定下一步动作
         │
         ├─ RootCompleted → commitRoot (提交)
         ├─ RootSuspended → 调度 fallback 延时 或 ping 后重新调度
         └─ RootInProgress → 时间片用完,重新调度 continue

步骤五、计算纠缠 lanes

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 状态不一致,并发渲染必须支持多优先级混合


步骤六、收尾并发入队更新

finishQueueingConcurrentUpdates();

作用

finishQueueingConcurrentUpdates 将渲染期间通过 concurrentQueues 累积的更新转移到 fiberupdateQueue.shared.pending 中,保证不丢失任何更新。在 prepareFreshStack 中调用它意味着在新渲染开始前,清理上一次渲染可能残留的未处理交插更新

设计思想

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


三、设计思想

  • 幂等清理:每次渲染前取消所有旧异步状态(定时器、pending commitpending effects),确保新渲染不受干扰
  • 双缓冲createWorkInProgress 不深度克隆,只复制引用,flags 清空,return 留空
  • 状态机:16 个全局变量规范渲染流程,workInProgressRootExitStatusRootInProgress 开始,逐步演变
  • 纠缠getEntangledLanes 保证逻辑批次的更新同时出现,防止 UI 撕裂
  • 渲染前入队清理finishQueueingConcurrentUpdates 处理完上一轮的残留交插更新后再开始新渲染
  • 并发安全:取消旧任务,不提交旧渲染,新任务覆盖旧任务
  • 预渲染优化workInProgressRootIsPrerendering 只在预渲染时设为 true,影响 work loop 的行为决策