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.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 的行为决策
相关推荐
用户298698530143 小时前
React 中实现 Word 与 Markdown 双向转换的实践记录
javascript·react.js·markdown
JavaPub-rodert5 小时前
开源一套 Go + React 后台管理系统,支持 RBAC 权限管理、Docker 一键部署!
react.js·golang·开源
kill5221 天前
useRef
前端·javascript·react.js
西柚小萌新1 天前
【LLM&&AI应用开发 八股文】--4.2.Agent智能体(中)
前端·javascript·react.js
Java后端的Ai之路1 天前
03_React_JSX
前端·react.js·前端框架
晴天小庭2 天前
Sael——基于Jev的AI中转站的安全风控网关,现已开源
人工智能·后端·react.js
Java后端的Ai之路2 天前
01-React基础教程
开发语言·前端·python·react.js·前端框架
流水白开2 天前
React的Virtual DOM、Diff算法和Fiber
前端·react.js
光影少年2 天前
如果 React 组件的属性没有传值,它的默认值是什么?
前端·javascript·react.js