Appearance
协调阶段的开始
协调阶段的开始是 React 整个更新流程中至关重要的一步,它标志着 React 开始计算和准备对 UI 进行实际更改。这个阶段的启动是由调度阶段(Scheduling Phase)完成的,具体来说,是由调度器(Scheduler)在浏览器空闲时回调执行的。
在调度阶段,当一个更新被触发后,最终会通过 ensureRootIsScheduled
函数,将一个回调函数(通常是 performSyncWorkOnRoot
或 performWorkOnRootViaSchedulerTask
)提交给 Scheduler(调度器)。Scheduler 会在浏览器主线程空闲时执行这个回调,从而正式开启协调阶段,这两个函数是协调阶段的真正入口。
performSyncWorkOnRoot(root)
: 当有同步更新(如高优先级事件回调、 ReactDOM.render 的首次渲染)时,调度器会安排这个函数。它会同步地执行协调工作,不中断地完成整个 Fiber 树的遍历。performWorkOnRootViaSchedulerTask(root, didTimeout)
: 当有并发更新(如 setState 、 useState 、 startTransition 等)时,调度器会安排这个函数。它会以可中断的方式执行协调工作,允许 React 在必要时暂停并交出控制权给浏览器,以响应用户输入或执行其他高优先级任务。
performSyncWorkOnRoot
performSyncWorkOnRoot 伪代码
javascript
function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes) {
// 1. 准备工作 (Preparation)
const originalIs = isWorking;
isWorking = true;
// 2. 执行同步渲染 (Perform Synchronous Render)
// 调用 renderRootSync 函数,它会同步地遍历 Fiber 树,执行组件的 render 方法,
// 比较新旧 Fiber 节点,并构建 workInProgress 树。这个过程是不可中断的。
let exitStatus = renderRootSync(root, lanes);
// 3. 处理结果 (Handle Result)
// 根据 renderRootSync 的执行结果(exitStatus)来决定后续操作。
if (exitStatus === RootDidComplete) {
// 如果协调工作成功完成,则准备进入提交阶段。
const finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
// 调用 finishSyncRender 来完成同步渲染,并触发提交阶段。
finishSyncRender(root, exitStatus, lanes);
} else if (exitStatus === RootFatalError) {
// 如果在协调过程中发生致命错误,则重置根节点的状态,并抛出错误。
// 这通常意味着 Fiber 树在处理过程中遇到了无法恢复的问题。
root.finishedWork = null;
root.finishedLanes = NoLanes;
// 清理可能存在的挂起任务或回调。
cancelCallback(root.callbackNode);
root.callbackNode = null;
root.callbackPriority = NoLane;
// 抛出错误,以便上层捕获和处理。
throw new Error('A fatal error occurred during synchronous rendering.');
} else {
// 对于同步工作,通常不会出现 RootDidNotComplete 或 RootSuspended 等状态,
}
// 恢复之前的工作状态。
isWorking = originalIsWorking;
}
详解上面的代码,我们就能够发现其内部完整流程。
准备工作 :
- isWorking = true; :这是一个内部标志,用于指示 React 当前正在执行工作。在同步工作期间,这个标志通常会设置为 true ,以防止其他并发任务干扰。
执行同步渲染 ( renderRootSync ) :
renderRootSync(root, lanes)
是performSyncWorkOnRoot
的核心。它负责执行实际的协调过程。对于同步更新,renderRootSync
会在一个不可中断的循环中遍历整个 Fiber 树,从根节点开始,向下递归地处理每个 Fiber 节点。- 在这个过程中,React 会执行组件的 render 方法,计算新的 props 和 state,并应用 Diff 算法来比较新旧 Fiber 节点。所有需要进行的 DOM 操作(如插入、更新、删除)都会被标记在 Fiber 节点上,形成一个副作用列表。
- 由于是同步工作, renderRootSync 会尝试一次性完成所有协调工作,不会进行时间切片或中断。
处理结果 :
exitStatus
: renderRootSync 函数的返回值,表示协调工作的最终状态。RootDidComplete
:如果 exitStatus 等于 RootDidComplete ,表示整个协调过程成功完成,并且 workInProgress 树已经构建完毕。此时, root.finishedWork 会指向这个新的 workInProgress 树, root.finishedLanes 记录了本次处理的优先级。finishSyncRender(root, exitStatus, lanes)
:这个函数会负责触发提交阶段(Commit Phase)。在commit phase章节会进行详细的讲解
RootFatalError
:如果 exitStatus 等于 RootFatalError ,表示在协调过程中发生了无法恢复的错误。React 会清理根节点的状态(如取消挂起的调度任务),并重新抛出这个错误,以便应用程序可以捕获并处理它。这通常发生在组件渲染过程中抛出未捕获的错误,导致 Fiber 树处于不一致状态时。- 其他状态:对于同步工作,理论上不会出现 RootDidNotComplete (未完成)或 RootSuspended (挂起)等状态,因为同步工作会一直执行直到完成或出错。如果出现,则表明存在异常情况,需要更复杂的错误处理逻辑。
performWorkOnRootViaSchedulerTask
performWorkOnRootViaSchedulerTask
是 React 调度器回调执行的入口,它负责在浏览器空闲时执行协调工作,其核心逻辑将更加清晰,主要聚焦于协调阶段的实际工作,他的主要工作包括
- 处理挂起的副作用 :首先刷新所有挂起的被动副作用(如 useEffect 的回调)。
- 获取工作优先级 :根据根节点当前的更新状态,确定本次协调需要处理的优先级。
- 执行协调工作 :进入工作循环,执行 Fiber 树的协调(render)工作,构建 workInProgress 树。
- 提交或中断 :根据协调结果,决定是提交渲染结果到 DOM,还是中断工作并重新调度。
performSyncWorkOnRoot 伪代码
javascript
function performWorkOnRootViaSchedulerTask(
root: FiberRoot,
didTimeout: boolean,
): RenderTaskFn | null {
// ... existing code ...
// 1. 处理挂起的副作用 (Flush pending passive effects)
const originalCallbackNode = root.callbackNode;
const didFlushPassiveEffects = flushPendingEffects(true);
if (didFlushPassiveEffects) {
// 如果副作用刷新导致当前任务被取消(例如,副作用中触发了新的高优先级更新),
// 则退出当前任务,等待新的调度。
if (root.callbackNode !== originalCallbackNode) {
root.callbackNode = null;
root.callbackPriority = NoLane;
return null;
}
}
// 2. 获取工作优先级 (Get work lanes)
let lanes = getLanesToSync(root);
if (lanes === NoLanes) {
// 如果没有需要同步处理的更新,则尝试获取下一个需要并发处理的更新。
lanes = getNextLanes(root, NoLanes, false);
if (lanes === NoLanes) {
// 如果仍然没有更新,则表示当前任务已完成,可以退出。
root.callbackNode = null;
root.callbackPriority = NoLane;
return null;
}
}
// 3. 执行协调工作 (Perform reconciliation work)
let exitStatus = renderRootSync(root, lanes);
// 4. 提交或中断 (Commit or yield)
if (exitStatus === RootFatalError) {
// 发生致命错误,重置状态并退出。
root.callbackNode = null;
root.callbackPriority = NoLane;
return null;
}
if (exitStatus === RootDidComplete) {
// 协调工作完成,准备进入提交阶段。
// 在提交之前,可能还需要处理一些收尾工作,例如调度被动效果。
const finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
finishConcurrentRender(root, exitStatus, lanes);
return null;
}
// 如果协调工作被中断(例如,时间切片用尽),则返回一个函数,
return performWorkOnRootViaSchedulerTask.bind(null, root);
}
详解上面的代码,我们就能够发现其内部完整流程。
处理挂起的副作用 ( flushPendingEffects ) :
- 在每次协调工作开始前,React 会确保所有挂起的被动副作用(如 useEffect 、 useLayoutEffect 的回调)都已执行。这是为了保证副作用的执行顺序和时机,避免在新的渲染周期开始前,旧的副作用还未清理或执行完毕。
- 如果
flushPendingEffects
导致root.callbackNode
发生变化(意味着有新的、更高优先级的任务被调度),则当前performWorkOnRootViaSchedulerTask
任务会立即退出,将控制权交还给调度器,以便处理新的高优先级任务。
获取工作优先级 :
- React 使用 Lanes 模型来管理更新的优先级。 getLanesToSync 会尝试获取需要同步处理的最高优先级更新。
- 如果不存在同步更新, getNextLanes 会获取下一个需要并发处理的更新。如果仍然没有更新,说明当前根节点没有待处理的任务,函数直接返回 null ,表示任务完成。
执行协调工作 (
renderRootSync
) :- 这是协调阶段的核心。
renderRootSync
函数(或其并发版本renderRootConcurrent
)会启动一个工作循环,遍历 Fiber 树。 - 在这个过程中,React 会比较当前 Fiber 树( current 树)和新的元素(JSX),生成新的 workInProgress 树。它会执行组件的 render 方法,计算新的 props 和 state,并进行 Diff 算法,标记需要更新的节点及其副作用(如插入、更新、删除等)。
- 这个过程是可中断的,如果时间切片用尽或者有更高优先级的任务介入, renderRootSync 可能会暂停并返回一个状态,等待调度器再次调用。
- 这是协调阶段的核心。
提交或中断 (
finishConcurrentRender
) :exitStatus
表示renderRootSync
的执行结果。- 如果
exitStatus
是RootFatalError
,表示在协调过程中发生了不可恢复的错误,当前任务会被终止。 - 如果
exitStatus
是RootDidComplete
,表示协调工作成功完成。此时,finishedWork
(即构建完成的workInProgress
树)会被赋值给root.finishedWork
,并调用finishConcurrentRender
来触发提交阶段(Commit Phase),将workInProgress
树的更改应用到实际的 DOM 上。 - 如果 exitStatus 表示协调工作被中断(例如,时间切片用尽),
performWorkOnRootViaSchedulerTask
会返回自身,以便调度器在下次空闲时继续执行未完成的工作。这体现了 React 并发模式的可中断性。
在上面的函数中,其内部会调用 renderRootSync
和 renderRootConcurrent
函数来执行实际的渲染工作。这两个函数都有各自的入口点,但它们的主要区别在于:
renderRootSync
: 这个函数会立即执行所有的同步渲染工作,直到所有的更新都被处理完毕。它会在当前的 JavaScript 执行栈上执行,不会创建新的栈帧。renderRootConcurrent
: 这个函数会在浏览器的空闲时间执行渲染工作。它会创建一个新的栈帧,在浏览器空闲时执行。
renderRootSync
renderRootSync
伪代码
javascript
// 主要职责:以同步、不可中断的方式执行 Fiber 树的协调和渲染工作。
function renderRootSync(root: FiberRoot, lanes: Lanes): RootExitStatus {
// 1. 记录原始的执行上下文,并设置当前为渲染上下文
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
// 2. 记录原始的调度优先级 (如果适用)
// 在同步模式下,通常会提升到最高优先级执行
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
// 3. 初始化或重置渲染相关的全局/模块级变量
// workInProgressRoot: 当前正在处理的 Root Fiber 的副本 (work-in-progress tree 的根)
// workInProgressRootRenderLanes: 当前渲染过程需要处理的 lanes
// workInProgress: 当前正在处理的 Fiber 节点
// ...其他状态变量
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
// 如果是新的渲染任务,或者 lanes 发生变化,需要准备新的栈帧
prepareFreshStack(root, lanes);
// workInProgressRoot = root;
// workInProgressRootRenderLanes = lanes;
// workInProgress = createWorkInProgress(root.current, null);
}
// 4. Profiler 相关逻辑 (如果开启)
// - 标记渲染开始
if (__DEV__ && enableProfilerTimer) {
markRenderPhaseStarted();
}
// 5. 执行同步工作循环 (Work Loop)
// 这是 Fiber 树遍历和协调的核心。
// workLoopSync 会持续执行,直到 workInProgress 为 null (即整棵树处理完毕)
// 或者遇到错误/挂起等情况。
do {
try {
workLoopSync(); // 核心的同步工作循环
break; // 如果 workLoopSync 正常完成 (没有抛出错误或被中断)
} catch (thrownValue) {
// 捕获在 workLoopSync 中发生的错误
handleError(root, thrownValue);
}
} while (true);
// 6. 清理工作
// - 重置执行上下文
executionContext = prevExecutionContext;
// - 重置 Dispatcher
ReactCurrentDispatcher.current = prevDispatcher;
// 7. Profiler 相关逻辑 (如果开启)
// - 标记渲染结束
if (__DEV__ && enableProfilerTimer) {
markRenderPhaseStopped();
}
// 8. 根据 workInProgressRoot 的状态返回退出状态 (RootExitStatus)
// - 如果 workInProgressRoot.shellSuspendCounter > 0 (或其他挂起标记),返回 RootSuspended
// - 如果发生错误 (例如 root.thrownErrors 有内容),返回 RootErrored 或 RootFatalErrored
// - 否则,返回 RootCompleted
if (workInProgressRootExitStatus !== RootInProgress) {
// 如果在 workLoopSync 内部已经设置了退出状态 (例如通过 throw)
return workInProgressRootExitStatus;
}
// 检查是否是由于 Suspense 导致的挂起
// (这是一个简化的表示,实际逻辑更复杂,涉及 Suspense Boundaries 和 fallback)
if (getWorkInProgressRootIsSuspended()) { // 假设有这样一个辅助函数
// 可能需要标记哪些 lanes 被挂起,以及设置 ping
// markRootSuspended(root, lanes);
return RootSuspended;
}
// 检查是否有未捕获的错误
if (root.thrownErrors !== null && root.thrownErrors.length > 0) {
// 根据错误类型判断是 RootErrored 还是 RootFatalErrored
return RootErrored; // 或 RootFatalErrored
}
// 如果一切顺利,返回完成状态
return RootCompleted;
}
// 辅助函数 handleError (简化版)
function handleError(root: FiberRoot, thrownValue: any): void {
// 重置 workInProgress 指针,以便从错误中恢复 (如果可能)
resetContextDependencies();
resetWorkInProgress();
// 记录错误信息到 root 上
// logError(root, thrownValue);
// workInProgressRootExitStatus = RootErrored;
// 向上查找错误边界 (Error Boundary)
// throwAndUnwindWorkLoop(root, thrownValue);
// 如果没有错误边界,或者错误发生在根节点,则标记为致命错误
}
// 核心同步工作循环 (简化版)
function workLoopSync(): void {
// 只要 workInProgress (当前工作单元) 不为 null,就持续执行
while (workInProgress !== null) {
performUnitOfWork(workInProgress); // 处理单个 Fiber 节点
}
}
renderRootSync
函数是 React Reconciler 中执行同步渲染的核心入口。当 React 决定以同步模式更新 UI 时(例如,在 Legacy 模式下,或通过 flushSync
调用,或处理高优先级的用户交互事件),就会调用此函数。它的主要职责是协调(Reconcile)Fiber 树,找出需要进行的更改,并为后续的提交阶段(Commit Phase)做准备。这个过程是不可中断的,会一次性完成。
其主要工作流程如下:
设置执行环境:
- 保存和设置执行上下文 (
executionContext
):函数首先会保存当前的executionContext
,然后将其设置为RenderContext
,表明 React 正处于渲染阶段。这有助于防止在渲染过程中发生不期望的重入行为。 - 设置 Dispatcher (
ReactCurrentDispatcher.current
): 为了确保在渲染期间 Hooks(如useState
,useEffect
)的行为符合预期(例如,在渲染期间调用setState
应该同步处理或报错),会暂时将当前的 Dispatcher 替换为一个仅包含上下文 API 的 Dispatcher (ContextOnlyDispatcher
) 或者一个适用于渲染阶段的特定 Dispatcher。
- 保存和设置执行上下文 (
初始化或重置渲染状态:
- 准备工作栈 (
prepareFreshStack
): 这是非常关键的一步。如果当前的workInProgressRoot
不是传入的root
,或者需要处理的lanes
发生了变化,意味着这是一个新的渲染任务或现有任务的参数已改变。此时会调用prepareFreshStack(root, lanes)
。prepareFreshStack
会创建或复用当前root
的alternate
Fiber 节点作为workInProgressRoot
(即正在构建的 Fiber 树的根)。- 它会重置
workInProgressRoot
的状态,例如清除之前的lanes
、effects
等,并设置新的renderLanes
。 - 最重要的是,它会将
workInProgressRoot
设置为当前的工作单元 (workInProgress
),作为 Fiber 树深度优先遍历的起点。
- 全局/模块级变量设置: 一些用于跟踪渲染过程的全局或模块级变量会被初始化或更新,例如
workInProgressRoot
(指向当前正在构建的树的根 Fiber)、workInProgressRootRenderLanes
(本次渲染需要处理的lanes
)。
- 准备工作栈 (
Profiler 标记:如果 Profiler 功能被启用(通常在开发模式下),会调用相关的 API(如
markRenderPhaseStarted()
)来标记渲染阶段的开始,以便进行性能分析。执行同步工作循环 (
workLoopSync
):这是renderRootSync
的核心。它会启动一个循环,只要还有工作单元 (workInProgress
不为null
),就会持续执行。- 在循环内部,会调用
performUnitOfWork(workInProgress)
来处理当前的 Fiber 节点。performUnitOfWork
会根据 Fiber 节点的类型执行不同的操作(例如,对于 HostComponent,会比较 props 并生成 DOM 更新;对于 FunctionComponent,会调用函数组件本身)。 performUnitOfWork
执行完毕后,会返回下一个- 要处理的 Fiber 节点(通常是子节点或兄弟节点),并更新
workInProgress
。 - 这个循环是同步的,会一直执行直到整棵 Fiber 树(或其需要更新的部分)都被处理完毕,即
workInProgress
变为null
。 - 错误处理:
workLoopSync
通常被包裹在一个try...catch
块中。如果在performUnitOfWork
过程中发生错误(例如,组件渲染时抛出异常),catch
块会捕获这个错误,并调用handleError
函数进行处理。handleError
可能会尝试寻找错误边界(Error Boundary)来优雅地处理错误,或者如果错误无法恢复,则标记整个渲染任务失败。
- 在循环内部,会调用
清理工作:
- 恢复执行上下文: 将
executionContext
恢复到进入renderRootSync
之前的状态。 - 恢复 Dispatcher: 将
ReactCurrentDispatcher.current
恢复到原始的 Dispatcher。
- 恢复执行上下文: 将
Profiler 标记:如果 Profiler 开启,调用
markRenderPhaseStopped()
标记渲染阶段结束。返回退出状态 (
RootExitStatus
):函数最后会根据渲染过程的结果返回一个状态码,告知调用者(如performSyncWorkOnRoot
)渲染的结果:RootCompleted
: 表示渲染成功完成,没有发生错误或挂起。RootSuspended
: 表示渲染过程中遇到了 Suspense 边界并且需要挂起(虽然在纯同步渲染中,挂起通常不期望发生,除非有特殊机制或与并发特性交互)。RootErrored
: 表示渲染过程中发生了可恢复的错误,并且可能已经被错误边界捕获。RootFatalErrored
: 表示发生了无法恢复的致命错误。workInProgressRootExitStatus
是一个在workLoopSync
内部可能被修改的变量,用于提前记录退出状态(例如,当错误发生时)。
renderRootSync
是 React 同步渲染机制的发动机。它通过一个严格的、不可中断的循环来构建或更新 Fiber 树,处理组件逻辑,并为最终的 DOM 更新(在 Commit 阶段)准备好所有必要的信息。其同步的特性保证了更新的即时性,但也意味着如果组件渲染逻辑复杂或耗时,可能会阻塞浏览器主线程。
renderRootConcurrent
renderRootConcurrent
函数解析
javascript
// renderRootConcurrent 函数 (位于 ReactFiberWorkLoop.js)
// 主要职责:以并发模式执行 Fiber 树的协调和渲染工作,允许任务中断和恢复。
function renderRootConcurrent(root: FiberRoot, lanes: Lanes): RootExitStatus {
// 1. 设置执行上下文为 RenderContext
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
const prevDispatcher = pushDispatcher(root.containerInfo); // 设置 Dispatcher
const prevAsyncDispatcher = pushAsyncDispatcher(); // 设置异步 Dispatcher
// 2. 准备工作栈 (Work-in-progress Stack)
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
Map 移动到 Set (movePendingFibersToMemoized)。
// 获取当前渲染的 transitions
workInProgressTransitions = getTransitionsForLanes(root, lanes);
// 重置渲染计时器 (resetRenderTimer)
resetRenderTimer();
// 准备新的工作栈 (prepareFreshStack)
prepareFreshStack(root, lanes);
} else {
// 如果是现有工作单元的延续,检查是否处于预渲染模式 (checkIfRootIsPrerendering)
workInProgressRootIsPrerendering = checkIfRootIsPrerendering(root, lanes);
}
// 3. Profiler 相关逻辑 (如果开启)
if (enableSchedulingProfiler) {
markRenderStarted(lanes); // 标记渲染开始
}
// 4. 执行并发工作循环 (Concurrent Work Loop)
outer: do {
try {
// 4.1 处理挂起状态 (Suspended State)
if (workInProgressSuspendedReason !== NotSuspended && workInProgress !== null) {
const unitOfWork = workInProgress;
const thrownValue = workInProgressThrownValue;
switch (workInProgressSuspendedReason) {
case SuspendedOnError:
// 解开栈 (Unwind) 然后继续正常的工作循环
// ... throwAndUnwindWorkLoop ...
break;
case SuspendedOnData: // 数据挂起
case SuspendedOnAction: // Action 挂起
// 检查 thenable 是否已经 resolve
// 如果已解决,则重播挂起的单元 (replaySuspendedUnitOfWork)
// 如果未解决,则添加回调,在解决后标记 root 可继续并调度 root,然后跳出外层循环 (break outer)
// ... thenable.then(onResolution, onResolution) ...
break;
case SuspendedOnImmediate: // 立即挂起 (通常为了给 ping 机会)
// 标记为 SuspendedAndReadyToContinue,然后跳出外层循环 (break outer)
break;
case SuspendedOnInstance: // 实例挂起 (如 HostComponent 资源加载)
// 标记为 SuspendedOnInstanceAndReadyToContinue,然后跳出外层循环 (break outer)
break;
case SuspendedAndReadyToContinue: // 已挂起并准备好继续
// 检查 thenable 是否已解决
// 如果已解决,重播挂起的单元 (replaySuspendedUnitOfWork)
// 否则,解开栈并继续
// ... throwAndUnwindWorkLoop ...
break;
case SuspendedOnInstanceAndReadyToContinue: // 实例已挂起并准备好继续
// 检查资源 (如 preloadInstance) 是否已准备好
// 如果已准备好,则恢复工作循环
// 否则,解开栈并继续
// ... throwAndUnwindWorkLoop ...
break;
case SuspendedOnDeprecatedThrowPromise: // 旧的 throw promise 模式
// 总是解开栈
// ... throwAndUnwindWorkLoop ...
break;
case SuspendedOnHydration: // Hydration 挂起
// 重置工作栈,设置退出状态为 RootSuspendedAtTheShell,跳出外层循环
break outer;
default:
// 抛出错误:未预期的挂起原因
throw new Error('Unexpected SuspendedReason.');
}
}
// 4.2 执行核心工作循环 (workLoopConcurrent)
workLoopConcurrentByScheduler();
break; // 如果 workLoopConcurrent 正常完成,跳出 do...while
} catch (thrownValue) {
// 4.3 捕获工作循环中发生的错误
handleError(root, thrownValue);
}
} while (true);
// 5. 清理工作
executionContext = prevExecutionContext;
// - 重置 Dispatcher
popDispatcher(prevDispatcher);
popAsyncDispatcher(prevAsyncDispatcher);
// 6. Profiler 相关逻辑 (如果开启)
if (enableSchedulingProfiler) {
if (workInProgressRootExitStatus === RootCompleted) {
markRenderStopped(lanes); // 标记渲染成功结束
} else if (workInProgressRootExitStatus === RootSuspended) {
markSuspendedRenderPhase(lanes); // 标记渲染挂起
} else if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
markSuspendedWithDelayPhase(lanes); // 标记渲染延迟挂起
} else if (workInProgressRootExitStatus === RootErrored) {
markErroredRenderPhase(lanes); // 标记渲染错误
} else if (workInProgressRootExitStatus === RootFatalErrored) {
markErroredRenderPhase(lanes); // 标记渲染致命错误
} else if (workInProgressRootExitStatus === RootSuspendedAtTheShell) {
markSuspendedRenderPhase(lanes); // 标记渲染在 Shell 处挂起
} else {
// 其他情况,如中断
markInterruptedRenderPhase(lanes);
}
}
// 7. 返回退出状态 (RootExitStatus)
return workInProgressRootExitStatus;
}
renderRootConcurrent
函数是 React Reconciler 中用于以并发(Concurrent)模式执行 Root 更新的核心函数。与 renderRootSync
的同步、不可中断执行不同,renderRootConcurrent
旨在利用浏览器空闲时间,将渲染工作拆分为可中断的小单元,从而避免阻塞主线程,提升用户体验。
详细解析:
renderRootConcurrent
函数是 React 并发模式下执行渲染工作的核心入口。它的主要目标是在不阻塞浏览器主线程的情况下,高效地处理 UI 更新。其工作流程与 renderRootSync
有许多相似之处,但在核心工作循环 (workLoopConcurrentByScheduler
) 和处理中断方面有所不同。
记录和设置执行上下文:
- 与
renderRootSync
类似,函数首先保存当前的executionContext
,并将其设置为RenderContext
。这确保了在渲染过程中,React 知道自己处于一个渲染上下文中,并防止不必要的重入。
- 与
记录和设置调度器:
- 保存当前的
ReactCurrentDispatcher.current
,并将其设置为ContextOnlyDispatcher
。这是 React Hooks 机制的一部分,确保 Hooks 只能在渲染阶段被调用。
- 保存当前的
初始化或重置渲染相关的全局/模块级变量 (
prepareFreshStack
):- 这是渲染准备阶段的关键一步。如果当前
root
或lanes
与上次渲染不同,prepareFreshStack(root, lanes)
会被调用。 prepareFreshStack
的作用是初始化或重置与当前渲染任务相关的全局状态,包括:- 设置
workInProgressRoot
为当前root
。 - 设置
workInProgressRootRenderLanes
为当前需要处理的lanes
。 - 创建
workInProgress
Fiber 树的根节点(通过createWorkInProgress(root.current, null)
),它是current
树的一个克隆,所有渲染工作都将在这个workInProgress
树上进行。 - 重置其他与渲染过程相关的内部状态,如错误边界信息、Suspense 状态等,确保每次渲染都从一个干净的状态开始。
- 设置
- 这是渲染准备阶段的关键一步。如果当前
Profiler 相关逻辑:
- 如果启用了 Profiler(通常在开发模式下),
markRenderPhaseStarted()
会被调用,用于记录渲染阶段的开始时间,以便开发者工具进行性能分析。
- 如果启用了 Profiler(通常在开发模式下),
执行并发工作循环 (
workLoopConcurrentByScheduler
):- 这是
renderRootConcurrent
的核心。与workLoopSync
不同,workLoopConcurrentByScheduler
是一个可中断的循环。 - 它会在每个时间切片(time slice)内执行一部分 Fiber 节点的协调工作。如果当前时间切片用尽,或者有更高优先级的任务到来,
workLoopConcurrentByScheduler
会暂停执行,并将控制权交还给浏览器,等待下一次调度。 - 这种机制使得 React 可以在不阻塞主线程的情况下,逐步完成复杂的 UI 更新,从而保持应用的响应性。
- 循环内部包含
try-catch
块,用于捕获在协调过程中可能抛出的错误,并通过handleError
进行处理。
- 这是
清理工作:
- 渲染循环结束后,
executionContext
会被恢复到进入renderRootConcurrent
之前的状态。 ReactCurrentDispatcher.current
也会被重置,以防止 Hooks 在渲染阶段之外被错误调用。
- 渲染循环结束后,
Profiler 相关逻辑:
- 如果启用了 Profiler,
markRenderPhaseStopped()
会被调用,记录渲染阶段的结束时间。
- 如果启用了 Profiler,
根据
workInProgressRoot
的状态返回退出状态 (RootExitStatus
):renderRootConcurrent
会根据workInProgressRoot
的最终状态返回一个RootExitStatus
,指示渲染的结果:RootCompleted
:表示渲染成功完成,可以进入提交阶段。RootSuspended
:表示渲染过程中遇到了 Suspense 组件,并且数据尚未准备好,渲染被挂起。此时,React 会等待数据加载完成后再继续渲染。RootErrored
或RootFatalErrored
:表示渲染过程中发生了错误。React 会根据错误的类型进行相应的处理,例如调度错误边界来显示错误 UI。
总结:
renderRootConcurrent
是 React 并发模式的核心驱动力。它通过时间切片和可中断的工作循环,实现了在保持 UI 响应性的同时处理复杂更新的能力。它与调度器紧密协作,确保渲染工作能够高效、平滑地进行,是 React 18+ 带来并发特性和 Suspense 的基石。
renderRootSync
和 renderRootConcurrent
函数是 React 协调阶段的入口,它们的主要职责是设置渲染环境,并根据更新的类型(同步或并发)调用相应的工作循环函数 ( workLoopSync
或 workLoopConcurrentByScheduler
) 来执行实际的 Fiber 树遍历和协调工作。 而 workLoopSync
和 workLoopConcurrentByScheduler
函数实质上调用 performUnitOfWork
函数开启 Fiber 树的深度优先遍历,完成 Fiber 树的构建和更新。而这也是下一章节的重点。
