performWorkOnRoot 源码解析

6 阅读5分钟

performWorkOnRoot 是 React 渲染工作流的总入口,从 FiberRoot 开始,完整执行一次渲染流程,最终把更新渲染到真实 DOM,是连接 调度层 (Scheduler)渲染层 (Reconciler)唯一桥梁

一、调用者


二、完整调用链(首次渲染)

performWorkOnRoot(root, DefaultLane, forceSync=false)
   │
   ├─ [1] 断言:未在渲染中(中断)
   │
   ├─ [2] shouldTimeSlice =
   │      (!forceSync &&!includesBlockingLane(lanes) &&
   │        !includesExpiredLane(root, lanes)) ||
   │        checkIfRootIsPrerendering(root, lanes)
   │    → shouldTimeSlice = false → 走 renderRootSync
   │    → shouldTimeSlice = true → 走 renderRootConcurrent
   │
   ├─ [3a] renderRootSync(root, DefaultLane, true)
   │
   ├─ [3b] renderRootConcurrent(root, DefaultLane)
   │
   ├─ [4] exitStatus !== RootInProgress(跳过)
   │
   ├─ [4a] renderWasConcurrent=false → 一致性检查(跳过)
   ├─ [4b] exitStatus !== RootErrored → 错误恢复(跳过)
   ├─ [4c] exitStatus !== RootFatalErrored → 致命错误(跳过)
   │
   └─ [4d] finishConcurrentRender
   │
   └─ [5] ensureRootIsScheduled(root)

三、代码解析

步骤一、防止递归渲染(安全锁)

if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
  throw new Error('Should not already be working.');
}

设计思想:防止在渲染 / 提交阶段再次触发新的渲染。渲染必须是单线程、原子化、不可重入的,避免状态混乱、死循环、内存撕裂


步骤二、本次渲染是否开启时间切片

const shouldTimeSlice =
  (!forceSync &&
    !includesBlockingLane(lanes) &&
    !includesExpiredLane(root, lanes)) ||
  checkIfRootIsPrerendering(root, lanes);

三个条件都满足才时间分片

条件含义
!forceSync不是强制同步
!includesBlockingLane(lanes)lanes 不包含阻塞 lane(SyncLane / InputContinuousLane / DefaultLane)
!includesExpiredLane(root, lanes)lanes 未过期(防饿死)
checkIfRootIsPrerendering(root, lanes)是预渲染模式

设计思想

  • 时间分片的本质是可中断渲染:每 5ms 让出主线程,处理用户交互
  • 同步 lanes(如 click 触发的 DiscreteEventPriority)必须立即完成,不中断
  • 过期 lane 意味着已被饿死过久,不中断以尽快输出
  • 预渲染例外:即使 sync lane 也走并发循环,避免阻塞主线程

步骤三、启动 renderRoot 构建 Fiber 树

let exitStatus: RootExitStatus = shouldTimeSlice
  ? renderRootConcurrent(root, lanes)
  : renderRootSync(root, lanes, true);

let renderWasConcurrent = shouldTimeSlice;

同步 vs 并发的关键区别

特性renderRootSyncrenderRootConcurrent
主循环workLoopSync()workLoopConcurrent()
中断从不中断,一次跑完每 5ms 检查 shouldYield
Suspense 处理unwind 立即回退可以暂停等待数据到达后恢复
返回状态RootCompleted / RootSuspendedAtTheShell还可以返回 RootInProgress(中断)

设计思想同一套渲染逻辑,两套执行策略。架构高度统一,又能支持同步 / 并发双模式


步骤四、进入渲染状态机循环(do-while (1))

do {
  if (exitStatus === RootInProgress) {
    // 中断渲染,让出主线程,并标记挂起
    if (workInProgressRootIsPrerendering && !shouldTimeSlice) {
      markRootSuspended(root, lanes, NoLane, false);
    }
    break;  // ← 等 Scheduler 下次安排
  } else {
    // 渲染完成(无论同步/异步/错误)

    // 一致性检查(并发渲染特有)
    const finishedWork = root.current.alternate;
    if (renderWasConcurrent && !isRenderConsistentWithExternalStores(finishedWork)) {
      exitStatus = renderRootSync(root, lanes, false);  // 同步重试
      renderWasConcurrent = false;
      continue;
    }

    // 错误恢复(新版并发模式特有)
    if ((disableLegacyMode || root.tag !== LegacyRoot) && exitStatus === RootErrored) {
      const errorRetryLanes = getLanesToRetrySynchronouslyOnError(root, lanes);
      if (errorRetryLanes !== NoLanes) {
        lanes = errorRetryLanes;
        exitStatus = recoverFromConcurrentError(root, lanes, errorRetryLanes);
        renderWasConcurrent = false;
        if (exitStatus !== RootErrored) continue;  // 恢复成功 → 重新开始
      }
    }

    // 致命错误
    if (exitStatus === RootFatalErrored) {
      prepareFreshStack(root, NoLanes);
      markRootSuspended(root, lanes, NoLane, true);
      break;
    }

    // 正常完成 → commit
    finishConcurrentRender(root, exitStatus, finishedWork, lanes, renderEndTime);
  }
  break;
} while (true);

exitStatus(渲染退出状态) 完整对照表

exitStatus含义finishConcurrentRender 处理
RootInProgress渲染未完成(中断/yield)不应到达此处 → throw Error
RootFatalErrored致命错误不应到达此处 → throw Error
RootCompleted完整渲染完成立即 commit
RootSuspendedSuspense 挂起(有 fallback)提交 fallback(立即或节流后)
RootSuspendedWithDelay超时挂起非 Transition → 提交 fallback;Transition → 继续等待
RootSuspendedAtTheShellShell 挂起不提交,继续等待
RootErrored渲染出错尝试 recovery,否则 commit 错误树

情况一、渲染还在进行中(未完成)

时间切片用完(5ms)→ 退出循环,让出主线程 → 浏览器可以重绘→ 下次空闲再恢复渲染

设计思想可中断、可恢复、可让步,保证页面绝不卡顿

情况二、校验外部状态一致性

并发渲染时,若外部 store(使用 useSyncExternalStore)被修改→ 导致渲染结果不一致→ 立刻同步重渲染,保证 UI 绝对正确

设计思想

  • 并发渲染必须保证最终一致性,宁可重新渲染,也不输出错误 UI,useSyncExternalStore 要求所有并发渲染看到的 store 状态一致
  • 若并发渲染中 store 被修改,之前的计算结果已无效 → 丢弃并同步重渲染,防止视觉撕裂
  • 同步渲染中外部事件不会交插执行(JS 单线程),所以同步渲染不需要此检查

情况三、处理渲染错误(Error Boundary

流程

  • 渲染出错 → exitStatus = RootErrored 且是新版并发模式
  • getLanesToRetrySynchronouslyOnError 获取可重试的 lanes(通常是排除错误 lane 后的剩余 lanes)
  • recoverFromConcurrentError 清除错误状态,用剩余 lanes 重新同步渲染

设计思想

  • 单个组件错误不影响整棵树:从错误中恢复,只重试未出错的 lanes
  • recoverFromConcurrentError 内部处理了水合失败的降级(从服务端渲染降级到客户端渲染)

情况四、处理致命错误

设计思想

  • 致命错误无法恢复 → 清空 workInProgress 栈,标记 root 挂起
  • markRootSuspendeddidAttemptEntireTree=true 参数确保不安排预渲染
  • React 不会因为致命错误崩溃整个应用,只是暂停当前渲染

情况五、渲染完成 → 进入 commit 提交阶段

流程

  • 处理 Suspense
  • 处理挂起
  • commitRoot → 真正更新 DOM

设计思想render 阶段和 commit 阶段严格分离

  • render:可中断、可重试、可恢复
  • commit:同步、一次性、不可中断

步骤五、重新调度下一次更新

ensureRootIsScheduled(root);

保证所有更新最终一定会执行,无论渲染结果如何(完成/挂起/错误)

  • 渲染已完成 → nextLanesNoLanes,在微任务中从调度链表移除
  • 渲染挂起 → nextLanes 可能还有工作(如更低优先级的 lane、被中断的 Transition),安排新任务
  • 新更新到达 → pendingLanes 已有标记,调度新任务

四、设计思想

设计决策解决问题
forceSync 禁用时间分片过期 lane 防饿死,同步更新需即时完成
shouldTimeSlice 三层检查精确控制何时可中断渲染
renderRootConcurrent + workLoopConcurrent可中断渲染的基础设施
renderRootSync + workLoopSync一次性渲染完成
一致性检查防止 useSyncExternalStore 撕裂
错误恢复循环单个组件错误不影响整棵树
确保最终调用 ensureRootIsScheduled本次渲染完成后所有 root 重新检查