Appearance
title: "React performUnitOfWork 工作单元处理机制:深度优先遍历的核心驱动" description: "【深入浅出 React 19:AI 视角下的源码解析与进阶】-深入解析 React performUnitOfWork 函数的工作原理,包括递归阶段、归并阶段、beginWork 和 completeUnitOfWork 的协作机制。" keywords:
- React performUnitOfWork
- Fiber 架构
- 深度优先遍历
- beginWork
- completeUnitOfWork
- 递归阶段
- 归并阶段
- workInProgress
- React 协调
- 工作单元 author: "React 源码解析"
category: "React 协调阶段" tags:
- React
- Fiber
- performUnitOfWork
- 协调阶段
- 源码解析 seo: canonical: "/react-code/reconciliation-phase/performUnitOfWork" openGraph: type: "article" title: "React performUnitOfWork 工作单元处理机制:深度优先遍历的核心驱动" description: "【深入浅出 React 19:AI 视角下的源码解析与进阶】-深入解析 React performUnitOfWork 函数的工作原理,包括递归阶段、归并阶段、beginWork 和 completeUnitOfWork 的协作机制。" image: "/images/react-performUnitOfWork.png" twitter: card: "summary_large_image" title: "React performUnitOfWork 工作单元处理机制:深度优先遍历的核心驱动" description: "【深入浅出 React 19:AI 视角下的源码解析与进阶】-深入解析 React performUnitOfWork 函数的工作原理,包括递归阶段、归并阶段、beginWork 和 completeUnitOfWork 的协作机制。" image: "/images/react-performUnitOfWork.png"
performUnitOfWork 工作单元处理机制
performUnitOfWork 函数是 React Fiber 架构中深度优先遍历的核心驱动。它通过调用 beginWork 来处理当前 Fiber 节点并生成其子节点,然后根据 beginWork 的结果决定是继续向下遍历子节点(更新 workInProgress 为子节点),还是结束当前节点的处理并开始回溯(调用 completeUnitOfWork)。这个过程不断重复,直到整棵 Fiber 树都被处理完毕。
它体现了 React 协调过程的两个主要阶段:“递”阶段 (Begin Phase) 和 “归”阶段 (Complete Phase)。
“递”阶段 (Begin Phase)
递阶段采用深度优先遍历策略:
- 如果
beginWork返回非null值(即有子节点),则继续向下遍历 - 将返回的子节点设置为新的
workInProgress,继续处理
beginWork 自顶向下遍历
beginWork 函数是 React 协调阶段(Reconciliation Phase)的核心函数之一,它负责自顶向下遍历 Fiber 树,为每个 Fiber 节点执行“开始工作”(begin work)。这个阶段的主要目标是确定当前 Fiber 节点是否需要更新,并创建或复用其子 Fiber 节点。beginWork 函数根据 Fiber 节点的 tag(类型)不同,调用相应的处理函数来处理不同类型的组件。
beginWork 函数的核心逻辑可以分为以下几个步骤:
核心逻辑
Bailout (优化) 检查 :
- 属性或上下文变化检测:如果当前 Fiber 节点存在(即不是新挂载的节点),它会比较
current.memoizedProps和workInProgress.pendingProps,以及检查hasLegacyContextChanged()。如果属性或旧版上下文发生变化,didReceiveUpdate标志会被设置为true。 - 调度更新或上下文变化检查:即使属性没有变化,也会检查是否有调度更新或上下文变化(通过
checkScheduledUpdateOrContext)。如果既没有调度更新也没有上下文变化,并且没有捕获到错误(DidCapture标志),则会尝试提前退出(bailout),跳过当前 Fiber 及其子树的协调工作。 - 强制更新:对于某些特殊情况,如
ForceUpdateForLegacySuspense标志,会强制进行更新。
- 属性或上下文变化检测:如果当前 Fiber 节点存在(即不是新挂载的节点),它会比较
处理新挂载的 Fiber 节点(
current === null):- 对于新挂载的 Fiber 节点,
didReceiveUpdate标志始终为false。 - 在水合(hydration)模式下,如果当前 Fiber 是分叉子节点(forked child),会处理
treeId的生成。
- 对于新挂载的 Fiber 节点,
清除待处理更新优先级:在进入
beginWork阶段之前,workInProgress.lanes会被设置为NoLanes,表示当前 Fiber 节点没有待处理的更新优先级。根据 Fiber 类型分发处理:这是
beginWork函数最核心的部分,它使用switch (workInProgress.tag)语句来根据 Fiber 节点的类型(tag)调用不同的更新或挂载函数。以下是一些主要 Fiber 类型的处理方式:LazyComponent:调用mountLazyComponent或updateLazyComponent(取决于current是否为null) 来处理懒加载组件。它会尝试解析LazyComponent,如果组件已经加载,则会像处理FunctionComponent或ClassComponent一样处理它。FunctionComponent:调用updateFunctionComponent来处理函数组件。这包括执行函数组件本身、处理hooks,并对其子节点进行reconcile。ClassComponent:调用updateClassComponent来处理类组件。这包括实例化组件、调用生命周期方法(如shouldComponentUpdate、getDerivedStateFromProps、render),并对其子节点进行reconcile。HostRoot:调用updateHostRoot来处理根Fiber节点。这通常涉及到处理根节点的children,并设置必要的上下文信息。HostComponent:调用updateHostComponent来处理原生 DOM 元素对应的Fiber节点(如<div>,<p>)。它主要负责处理该元素的children。HostText:调用updateHostText来处理文本节点。文本节点没有children,所以此函数通常返回null。HostPortal:调用updateHostPortal来处理Portal。它会处理Portal的children,但这些children会被渲染到Portal指定的不同 DOM 容器中。SuspenseComponent:调用updateSuspenseComponent来处理<Suspense>组件。它负责协调主内容和fallbackUI,处理挂起状态。SuspenseListComponent:调用updateSuspenseListComponent来处理<SuspenseList>组件。它用于协调多个<Suspense>或可挂起组件的显示顺序和加载行为。ForwardRef:调用updateForwardRef来处理通过React.forwardRef创建的组件。MemoComponent:调用updateMemoComponent来处理通过React.memo优化的组件。它会比较props来决定是否需要重新渲染。SimpleMemoComponent:在updateSimpleMemoComponent中处理,这是对MemoComponent的一种优化,当比较函数简单时使用。Block:(实验性) 调用updateBlock来处理React.Block组件。OffscreenComponent:调用updateOffscreenComponent来处理用于实现屏幕外渲染的组件,例如在<Suspense>的fallback期间或未来的并发特性中。LegacyHiddenComponent:调用updateLegacyHiddenComponent来处理旧版的<LegacyHidden>组件,用于控制子树的可见性。ScopeComponent:(实验性) 调用updateScopeComponent来处理React Scope组件,用于事件处理和焦点管理。ContextConsumer:调用updateContextConsumer来处理使用旧版contextType或Context.Consumer的组件。ContextProvider:调用updateContextProvider来处理Context.Provider组件,它会更新Context的值并向下传递。Profiler:调用updateProfiler来处理<Profiler>组件,用于性能分析。DehydratedFragment:在updateDehydratedFragment中处理,用于服务端渲染 (SSR) 和hydration过程中的一部分。IncompleteClassComponent:这是一个特殊标记,表示类组件在之前的渲染中未完成。beginWork会尝试再次处理它。CacheComponent:(实验性) 调用updateCacheComponent来处理用于数据缓存的组件。TracingMarkerComponent:(实验性,与enableTransitionTracing相关) 调用updateTracingMarkerComponent来处理用于追踪transition的标记组件。ActivityComponent:(实验性) 调用updateActivityComponent来处理用于实现React.Activity的组件。
错误处理:如果遇到未知的 Fiber 节点类型,
beginWork会抛出一个错误。
mountXXX 和 updateXXX函数的区别
在 beginWork 阶段,React 通过检查 current Fiber 是否为 null 来判断是挂载还是更新。基于这个判断,它会调用相应的 mountXXX 或 updateXXX 函数变体 (或者在同一个 updateXXX 函数内部通过 if (current === null) 来区分逻辑分支)。这种区分的核心原因在于 组件生命周期的不同阶段及其对应的特定逻辑和优化机会。要知道beginWork 的主要职责之一就是判断当前是在处理一个全新的节点(挂载)还是一个已存在的节点(更新)。
current Fiber 的作用
- 当一个 Fiber 节点首次被创建和渲染时,它对应的
currentFiber 是null。这意味着这个节点在屏幕上还没有对应的已提交版本。 - 当一个 Fiber 节点发生更新时,它会有一个非
null的currentFiber,这个currentFiber 代表了该节点在上一次成功渲染并提交到屏幕上的状态。
mountXXX 和 updateXXX 的区别
初始化 vs. 比较与复用:
mountXXX系列函数:在current === null的情况下被调用。它们负责组件的首次初始化。- 创建实例:对于类组件,需要创建新的组件实例。
- 执行构造函数和初始渲染:调用
constructor(类组件),执行函数组件体。 - 设置初始
state和props。 - 创建新的子 Fiber 节点:因为是首次渲染,所有子节点也都是全新的。
- 标记为
Placement:新创建的 DOM 节点需要在 commit 阶段被插入到 DOM 中。
updateXXX系列函数:在current !== null的情况下被调用。它们负责组件的更新逻辑。- 比较
props和context:检查是否有变化,这是进行bailout(优化,跳过渲染) 的重要依据。 - 调用更新相关的生命周期方法:如
shouldComponentUpdate、getDerivedStateFromProps(类组件)。 - 复用实例:通常会复用
currentFiber 上的组件实例 (类组件)。 diff子节点:调用reconcileChildren时,会比较current.child和新的children元素,以决定是复用、更新、移动还是删除子 Fiber。- 标记副作用:根据
diff结果和组件逻辑,标记相应的副作用 (如Update、Deletion等)。
- 比较
生命周期方法的调用:
mountXXX过程中会触发挂载相关的生命周期方法 (如componentDidMount会在 commit 阶段被调度)。updateXXX过程中会触发更新相关的生命周期方法 (如componentDidUpdate会在 commit 阶段被调度)。
性能优化路径:
updateXXX路径有更多的优化机会。最典型的就是bailout逻辑:如果props、context没有变化,并且没有强制更新的标记,React 可能会直接复用currentFiber 的子树,跳过对其子组件的beginWork和completeWork阶段,从而显著提升性能。mountXXX路径通常没有这样的bailout机会,因为它必须完成组件的初始化和首次渲染。
Hooks 的行为:
- 对于函数组件,Hooks (如
useState,useEffect) 在挂载和更新时的行为也是不同的。useState的初始值在挂载时使用。useEffect的回调函数在挂载后执行 (如果依赖数组为空或未提供),其清理函数在组件卸载前执行。在更新时,如果依赖项发生变化,则先执行上一次的清理函数,再执行新的回调函数。 这些差异也是在mountXXX和updateXXX(具体来说是renderWithHooks内部) 中处理的。
- 对于函数组件,Hooks (如
这种区分使得 React 能够:
- 执行特定于挂载或更新的初始化和清理逻辑。
- 调用正确的生命周期方法。
- 应用不同的性能优化策略,特别是针对更新路径的
bailout机制。 - 正确处理 Hooks 在不同阶段的行为。
updateXXX 函数的主要职责
- 对于组件 Fiber :调用组件的
render方法(类组件)或函数本身(函数组件),获取其子ReactElement。 - 对于 Host Fiber :处理其属性。
- 调用
reconcileChildren:这个函数(或其变体如reconcileChildFibers)是 Diff 算法的核心实现。它会将新的子ReactElement与 current Fiber 的旧子 Fiber 进行比较,生成新的子 workInProgress Fiber 链表,并标记出需要进行的 DOM 操作(通过设置 Fiber 节点的 flags )。 - 返回第一个子 Fiber :
updateXXX函数最终会返回新生成的子 Fiber 链表的第一个节点,作为beginWork的返回值。如果当前 Fiber 没有子节点,则返回 null 。
mountXXX 函数的主要职责
- 初始化 :创建组件实例 (如果需要),设置初始状态和属性。
- 执行渲染逻辑 :调用组件的渲染函数/方法以获得其子元素描述。
- 生成子 Fiber :为所有子元素创建新的 Fiber 节点,并标记它们为需要插入到 DOM 中 (
Placement)。 - 准备下一次工作 :返回第一个子 Fiber 作为下一个工作单元,或者返回 null
与 updateXXX 函数相比, mountXXX 函数通常不涉及与 current Fiber 的比较 (因为 current 为 null ),也没有复杂的 bailout (跳过渲染) 逻辑。它们的主要任务是**“从无到有”**地构建 Fiber 节点及其子树。
返回值
Fiber | null- 如果当前
workInProgressFiber 有子节点,beginWork会返回其第一个子 Fiber 节点。这个子节点将成为performUnitOfWork中的下一个workInProgress,继续进行深度优先遍历。 - 如果当前
workInProgressFiber 没有子节点(例如,一个文本节点,或者一个组件render返回了null或空数组),或者它是一个bailout的情况并且没有新的子节点,beginWork会返回null。这告诉performUnitOfWork当前节点的“递”阶段结束,应该开始处理“归”阶段(即调用completeUnitOfWork)。
- 如果当前
beginWork 函数核心逻辑
javascript
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (
oldProps !== newProps ||
hasLegacyContextChanged()
) {
didReceiveUpdate = true;
} else {
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (
!hasScheduledUpdateOrContext &&
(workInProgress.flags & DidCapture) === NoFlags
) {
didReceiveUpdate = false;
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
if (getIsHydrating() && isForkedChild(workInProgress)) {
const slotIndex = workInProgress.index;
const numberOfForks = getForksAtLevel(workInProgress);
pushTreeId(workInProgress, numberOfForks, slotIndex);
}
}
workInProgress.lanes = NoLanes;
switch (workInProgress.tag) {
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
renderLanes,
);
}
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
disableDefaultPropsExceptForClasses ||
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultPropsOnNonClassComponent(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps = resolveClassComponentProps(
Component,
unresolvedProps,
workInProgress.elementType === Component,
);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostHoistable:
if (supportsResources) {
return updateHostHoistable(current, workInProgress, renderLanes);
}
case HostSingleton:
if (supportsSingletons) {
return updateHostSingleton(current, workInProgress, renderLanes);
}
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(current, workInProgress, renderLanes);
case HostPortal:
return updatePortalComponent(current, workInProgress, renderLanes);
case ForwardRef: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
disableDefaultPropsExceptForClasses ||
workInProgress.elementType === type
? unresolvedProps
: resolveDefaultPropsOnNonClassComponent(type, unresolvedProps);
return updateForwardRef(
current,
workInProgress,
type,
resolvedProps,
renderLanes,
);
}
case Fragment:
return updateFragment(current, workInProgress, renderLanes);
case Mode:
return updateMode(current, workInProgress, renderLanes);
case Profiler:
return updateProfiler(current, workInProgress, renderLanes);
case ContextProvider:
return updateContextProvider(current, workInProgress, renderLanes);
case ContextConsumer:
return updateContextConsumer(current, workInProgress, renderLanes);
case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
let resolvedProps = disableDefaultPropsExceptForClasses
? unresolvedProps
: resolveDefaultPropsOnNonClassComponent(type, unresolvedProps);
resolvedProps = disableDefaultPropsExceptForClasses
? resolvedProps
: resolveDefaultPropsOnNonClassComponent(type.type, resolvedProps);
return updateMemoComponent(
current,
workInProgress,
type,
resolvedProps,
renderLanes,
);
}
case SimpleMemoComponent: {
return updateSimpleMemoComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
renderLanes,
);
}
case IncompleteClassComponent: {
if (disableLegacyMode) {
break;
}
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps = resolveClassComponentProps(
Component,
unresolvedProps,
workInProgress.elementType === Component,
);
return mountIncompleteClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case IncompleteFunctionComponent: {
if (disableLegacyMode) {
break;
}
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps = resolveClassComponentProps(
Component,
unresolvedProps,
workInProgress.elementType === Component,
);
return mountIncompleteFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case SuspenseListComponent: {
return updateSuspenseListComponent(current, workInProgress, renderLanes);
}
case ScopeComponent: {
if (enableScopeAPI) {
return updateScopeComponent(current, workInProgress, renderLanes);
}
break;
}
case ActivityComponent: {
return updateActivityComponent(current, workInProgress, renderLanes);
}
case OffscreenComponent: {
return updateOffscreenComponent(
current,
workInProgress,
renderLanes,
workInProgress.pendingProps,
);
}
case LegacyHiddenComponent: {
if (enableLegacyHidden) {
return updateLegacyHiddenComponent(
current,
workInProgress,
renderLanes,
);
}
break;
}
case CacheComponent: {
return updateCacheComponent(current, workInProgress, renderLanes);
}
case TracingMarkerComponent: {
if (enableTransitionTracing) {
return updateTracingMarkerComponent(
current,
workInProgress,
renderLanes,
);
}
break;
}
case ViewTransitionComponent: {
if (enableViewTransition) {
return updateViewTransition(current, workInProgress, renderLanes);
}
break;
}
case Throw: {
throw workInProgress.pendingProps;
}
}
throw new Error(
`Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
'React. Please file an issue.',
);
}
export {beginWork};内部核心函数
renderWithHooks
renderWithHooks 函数位于 ReactFiberHooks.js 文件中,是 React Hooks机制的核心入口。当一个函数组件或者其他使用 Hooks 的组件(如通过 forwardRef 包裹并使用 Hooks 的组件)进行渲染或更新时,beginWork 阶段的相应 update 函数(例如 updateFunctionComponent)会调用 renderWithHooks 来执行组件的渲染逻辑并处理 Hooks。
核心内部步骤:
初始化全局 Hooks 状态:
- 设置
renderLanes = nextRenderLanes; - 设置
currentlyRenderingFiber = workInProgress; - 重置当前 Fiber (
workInProgress) 的 Hooks 相关状态:workInProgress.memoizedState = null;(Hooks 状态链表的头)workInProgress.updateQueue = null;(Effects 列表等)workInProgress.lanes = NoLanes;
- 重置全局变量如
currentHook = null;,workInProgressHook = null;,didScheduleRenderPhaseUpdate = false;等。
- 设置
选择 Hooks Dispatcher:
ReactSharedInternals.H(全局 Hooks Dispatcher 对象) 会被设置为:- 如果
current === null或者current.memoizedState === null(表示是 Mount 阶段,或者 Update 阶段但之前没有状态型 Hook),则设置为HooksDispatcherOnMount。 - 否则 (表示是 Update 阶段且之前有状态型 Hook),则设置为
HooksDispatcherOnUpdate。
- 如果
HooksDispatcherOnMount和HooksDispatcherOnUpdate包含了各个 Hook API (如useState,useEffect) 在 Mount 和 Update 阶段的不同实现。
执行组件函数:
let children = Component(props, secondArg);- 在这一步,组件内部调用的
useState,useEffect等会通过ReactSharedInternals.H指向的 Dispatcher 执行。- Mount 时,Hook 会创建新的 Hook 对象并加入到
workInProgress.memoizedState链表中。 - Update 时,Hook 会从
current.memoizedState中找到对应的旧 Hook 对象,计算新状态,并更新workInProgress.memoizedState。
- Mount 时,Hook 会创建新的 Hook 对象并加入到
处理渲染阶段的更新 (Render Phase Updates):
- 检查
didScheduleRenderPhaseUpdateDuringThisPass标志位(这个标志位在 Hook 的 state 更新函数被调用时,如果是在渲染阶段,则会被设为 true)。 - 如果为
true,说明在本次组件渲染过程中,有 Hook 触发了状态更新,需要重新渲染组件以获取最新的状态。 - 此时会调用
renderWithHooksAgain(workInProgress, Component, props, secondArg);来重新执行组件的渲染逻辑。这个过程可能会重复,直到没有新的渲染阶段更新(组件状态稳定)。
- 检查
完成 Hooks 渲染并清理:
- 调用
finishRenderingHooks(current, workInProgress, Component);- 将
ReactSharedInternals.H重置为ContextOnlyDispatcher(一个只包含readContext的 dispatcher,防止在渲染后意外调用其他 Hooks) 这是一个关键步骤,它确保在组件渲染逻辑之外(例如,在 useEffect 的清理函数或异步回调中)不能再调用除 useContext (通过 readContext) 之外的 Hooks,否则会报错。这有助于维持 Hooks 调用的规则(只能在顶层和自定义 Hook 中调用)。 - 重置全局 Hooks 状态变量,如
renderLanes,currentlyRenderingFiber,currentHook,workInProgressHook,didScheduleRenderPhaseUpdate等。
- 将
- 调用
返回渲染结果:
return children;
移除开发代码后的伪代码:
renderWithHooks函数
javascript
function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
// 1. 初始化全局和 Fiber 的 Hooks 状态
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
// 重置其他全局 Hooks 状态变量 (currentHook, workInProgressHook, didScheduleRenderPhaseUpdate等)
resetHooksState();
// 2. 选择 Hooks Dispatcher
if (current === null || current.memoizedState === null) {
ReactSharedInternals.H = HooksDispatcherOnMount;
} else {
ReactSharedInternals.H = HooksDispatcherOnUpdate;
}
// 3. 执行组件函数
let children = Component(props, secondArg);
// 4. 处理渲染阶段的更新
if (didScheduleRenderPhaseUpdateDuringThisPass) {
// 循环调用,直到组件状态稳定
children = renderWithHooksAgain(workInProgress, Component, props, secondArg);
}
// 5. 完成 Hooks 渲染并清理
finishRenderingHooks(current, workInProgress, Component);
// 6. 返回渲染结果
return children;
}
// 辅助函数(概念性)
function resetHooksState() {
currentHook = null;
workInProgressHook = null;
didScheduleRenderPhaseUpdate = false;
didScheduleRenderPhaseUpdateDuringThisPass = false; // 在 renderWithHooksAgain 中会重置
// ... 其他状态
}renderWithHooks 是 React Hooks 能够工作的基石,它通过精巧的状态管理和 Dispatcher 模式,使得开发者可以在函数组件中拥有状态、副作用等能力,同时保持了组件的纯粹性和可测试性。
reconcileChildren
javascript
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
if (current === null) {
// --- 首次挂载 (Mounting) ---
workInProgress.child = mountChildFibers(
workInProgress, // 父 Fiber
null, // 旧的第一个子 Fiber (因为是 mount,所以为 null)
nextChildren, // 新的子元素
renderLanes
);
} else {
// --- 更新 (Updating) ---
workInProgress.child = reconcileChildFibers(
workInProgress, // 父 Fiber
current.child, // 旧的第一个子 Fiber
nextChildren, // 新的子元素
renderLanes
);
}
}reconcileChildren 函数位于 ReactFiberBeginWork.js 文件中,它是 beginWork 阶段处理大部分类型 Fiber 节点时,进行子节点协调(Reconciliation)的入口函数。其核心职责是根据新的子节点数据(nextChildren)和当前 Fiber 节点的状况(是否存在 current Fiber,即旧 Fiber 树对应的节点),来决定如何处理子 Fiber。
大体上,reconcileChildren 的内部步骤可以概括为:
- 识别 Mount 或 Update 场景。
- 如果是 Mount:调用
mountChildFibers,它通过reconcileChildFibersImpl(以shouldTrackSideEffects = false的模式) 创建所有新的子 Fiber,并标记为Placement。 - 如果是 Update:调用
reconcileChildFibers,它通过reconcileChildFibersImpl(以shouldTrackSideEffects = true的模式) 对比新旧子节点,执行 Diff 算法,复用、创建、更新或删除子 Fiber,并标记相应的副作用 (Placement,Update,Deletion)。 reconcileChildFibersImpl根据newChild的具体类型(单一元素、数组、文本等)分发到更具体的协调函数进行处理。- 将协调产生的子 Fiber 链表链接到
workInProgressFiber 上。 - 返回第一个子 Fiber,供
performUnitOfWork继续处理。
这个过程是 React 实现高效 UI 更新的核心机制之一,通过 Diffing 算法最小化 DOM 操作,并利用 Fiber 架构实现可中断和可恢复的渲染。
流程图
总结
beginWork 函数是 React 协调阶段的起点,它通过自顶向下的方式处理 Fiber 节点,决定每个节点的更新策略。它根据 Fiber 节点的类型,将工作分发给不同的处理函数,从而实现了 React 对各种组件和特性的灵活支持。通过对 current Fiber 节点和 workInProgress Fiber 节点的比较,以及对更新优先级的检查,beginWork 能够有效地跳过不必要的协调工作,从而优化渲染性能。
“归”阶段 (Complete Phase)
当一个节点没有子节点或者所有子节点都处理完毕后,表示当前节点没有子节点需要处理,此时进入归阶段,由 completeUnitOfWork (内部调用 completeWork) 执行,处理兄弟节点,向上回溯到父节点。
completeWork
completeWork 函数是 React 渲染阶段“归”过程的核心,当一个 Fiber 节点及其所有子节点都完成了 beginWork(“递”过程)后,completeWork 会被调用来完成该 Fiber 节点的工作。
它的核心职责包括:
- 创建/更新 DOM 实例 (对于 HostComponent 和 HostText):
- 对于初次渲染 (
current === null):HostComponent: 调用createInstance创建 DOM 元素,然后调用appendAllChildren将子 DOM 节点附加到新创建的父 DOM 元素上,最后调用finalizeInitialChildren设置 DOM 元素的属性、事件监听器等。HostText: 调用createTextInstance创建文本节点。
- 对于更新 (
current !== null):HostComponent: 如果updatePayload(在beginWork中通过diffProperties计算得出) 不为 null,则标记Updateflag,表明在 Commit 阶段需要更新 DOM 属性。HostText: 如果文本内容改变,调用updateHostText标记Updateflag。
- 对于初次渲染 (
- 处理副作用 (Side Effects): 根据
beginWork阶段和当前completeWork阶段的工作,标记 Fiber 节点的flags(例如Placement,Update,Deletion,Hydrating等)。这些flags会在 Commit 阶段被用来执行实际的 DOM 操作或其他副作用。 - 冒泡属性 (Bubble Properties): 调用
bubbleProperties函数,将子 Fiber 的lanes和subtreeFlags冒泡到当前 Fiber。subtreeFlags用于快速判断子树中是否存在需要处理的副作用,避免不必要的遍历。 - 处理特定的组件类型:
FunctionComponent,MemoComponent,SimpleMemoComponent,ForwardRef: 通常没有与 DOM 直接相关的completeWork逻辑,主要是bubbleProperties。ClassComponent: 类似于函数组件,主要进行bubbleProperties。如果组件是旧版的 Context Provider,会调用popLegacyContext。HostRoot: 对于根节点,如果是 Mutation 模式,会调用updateHostContainer;如果是 Hydration 模式,会处理 Hydration 相关逻辑。最终会popHostContainer和popTopLevelLegacyContextObject。HostPortal: 弹出HostContainer上下文,并调用updateHostContainer。SuspenseComponent: 处理 Suspense 边界的完成逻辑,包括判断是否超时、是否需要显示 fallback、处理retryQueue、标记Visibilityflag 等。会调用popSuspenseHandler。SuspenseListComponent: 处理 SuspenseList 的复杂逻辑,协调子 SuspenseComponent 的显示顺序和 fallback 状态。会调用popSuspenseListContext。OffscreenComponent,LegacyHiddenComponent: 处理屏幕外或隐藏组件的逻辑,例如标记Visibilityflag,弹出SuspenseHandler和HiddenContext。ContextProvider: 弹出 Context Provider。调用popProvider。CacheComponent: 处理缓存组件的逻辑,例如比较前后缓存是否变化,如果变化则标记Passiveflag 以执行副作用。调用popCacheProvider。ScopeComponent: 如果启用了 Scope API,会创建或更新 Scope 实例,并标记Update(如果存在 ref)。TracingMarkerComponent: 如果启用了 Transition Tracing,会弹出MarkerInstance。
- 返回下一个工作单元:
completeWork通常返回null,表示当前 Fiber 的工作完成,控制权交还给workLoop,workLoop会继续处理当前 Fiber 的兄弟节点或父节点。但在某些特殊情况下(例如 SuspenseList 需要重新渲染子项),它可能会返回一个子 Fiber 作为下一个工作单元。
移除开发代码后的伪代码:
javascript
function completeWork(current, workInProgress, renderLanes) {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent:
case ClassComponent: // (Simplified, actual class components have more logic for lifecycles etc. in beginWork)
bubbleProperties(workInProgress);
return null;
case HostRoot:
popHostContainer(workInProgress);
popTopLevelLegacyContextObject(workInProgress);
// Reset hydration state
if (workInProgress.flags & Hydrating) {
workInProgress.flags &= ~Hydrating;
resetHydrationState();
}
updateHostContainer(workInProgress);
bubbleProperties(workInProgress);
return null;
case HostComponent:
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
// Update path
const oldProps = current.memoizedProps;
const instance = workInProgress.stateNode;
const updatePayload = workInProgress.updateQueue; // Prepared in beginWork
workInProgress.updateQueue = null;
if (updatePayload) {
markUpdate(workInProgress);
}
} else {
// Mount path
const instance = createInstance(type, newProps, rootContainerInstance, /*hostContext*/ null, workInProgress);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
markUpdate(workInProgress);
}
}
bubbleProperties(workInProgress);
return null;
case HostText:
const newText = newProps;
if (current && workInProgress.stateNode != null) {
// Update path
const oldText = current.memoizedProps;
if (oldText !== newText) {
markUpdate(workInProgress);
}
} else {
// Mount path
workInProgress.stateNode = createTextInstance(newText, getRootHostContainer(), getHostContext(), workInProgress);
}
bubbleProperties(workInProgress);
return null;
case SuspenseComponent:
popSuspenseHandler(workInProgress);
const nextState = workInProgress.memoizedState;
if (workInProgress.flags & DidCapture) {
// Something suspended, re-render with fallback
workInProgress.lanes = renderLanes;
return workInProgress; // Retry this fiber
}
// ... (logic for toggling visibility, scheduling effects for transitions)
bubbleProperties(workInProgress);
return null;
case HostPortal:
popHostContainer(workInProgress);
updateHostContainer(current, workInProgress); // For portals, this appends children to the portal container
// No children to bubble from a portal directly.
return null;
case ContextProvider:
const context = workInProgress.type._context; // Simplified
popProvider(context, workInProgress);
bubbleProperties(workInProgress);
return null;
case OffscreenComponent:
case LegacyHiddenComponent: // Simplified
popSuspenseHandler(workInProgress);
popHiddenContext(workInProgress);
const nextOffscreenState = workInProgress.memoizedState;
const nextIsHidden = nextOffscreenState !== null;
if (current !== null) {
const prevIsHidden = current.memoizedState !== null;
if (prevIsHidden !== nextIsHidden) {
workInProgress.flags |= Visibility;
}
} else {
if (nextIsHidden) {
workInProgress.flags |= Visibility;
}
}
if (!nextIsHidden || (workInProgress.mode & ConcurrentMode) === NoMode) {
bubbleProperties(workInProgress);
}
// ... (logic for cache and transitions)
return null;
// ... other cases like SuspenseListComponent, CacheComponent, etc. would follow a similar pattern:
// 1. Pop any relevant contexts.
// 2. Perform type-specific logic (e.g., managing state, flags).
// 3. Call bubbleProperties.
// 4. Return null or a new fiber to work on.
default:
// This should not happen
throw new Error('Unknown unit of work tag');
}
}completeWork 是 React Render 阶段的关键部分,它与 beginWork 协同工作,构建出带有副作用标记的 Fiber 树(称为 “finished work” 或 “effect list”),这个树随后会在 Commit 阶段被处理,从而实现 UI 的更新。
performUnitOfWork 正是连接这两个阶段,并驱动整个工作循环向前进行的关键函数。
