Appearance
title: "React performUnitOfWork 工作单元处理机制:深度优先遍历的核心驱动" description: "深入解析 React performUnitOfWork 函数的工作原理,包括递归阶段、归并阶段、beginWork 和 completeUnitOfWork 的协作机制。" keywords:
- React performUnitOfWork
- Fiber 架构
- 深度优先遍历
- beginWork
- completeUnitOfWork
- 递归阶段
- 归并阶段
- workInProgress
- React 协调
- 工作单元 author: "React 源码解析" date: "2024-01-15" updated: "2024-01-15" category: "React 协调阶段" tags:
- React
- Fiber
- performUnitOfWork
- 协调阶段
- 源码解析 seo: canonical: "/react-code/reconciliation-phase/performUnitOfWork" openGraph: type: "article" title: "React performUnitOfWork 工作单元处理机制:深度优先遍历的核心驱动" description: "深入解析 React performUnitOfWork 函数的工作原理,包括递归阶段、归并阶段、beginWork 和 completeUnitOfWork 的协作机制。" image: "/images/react-performUnitOfWork.png" twitter: card: "summary_large_image" title: "React performUnitOfWork 工作单元处理机制:深度优先遍历的核心驱动" description: "深入解析 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>
组件。它负责协调主内容和fallback
UI,处理挂起状态。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 节点首次被创建和渲染时,它对应的
current
Fiber 是null
。这意味着这个节点在屏幕上还没有对应的已提交版本。 - 当一个 Fiber 节点发生更新时,它会有一个非
null
的current
Fiber,这个current
Fiber 代表了该节点在上一次成功渲染并提交到屏幕上的状态。
mountXXX
和 updateXXX
的区别
初始化 vs. 比较与复用:
mountXXX
系列函数:在current === null
的情况下被调用。它们负责组件的首次初始化。- 创建实例:对于类组件,需要创建新的组件实例。
- 执行构造函数和初始渲染:调用
constructor
(类组件),执行函数组件体。 - 设置初始
state
和props
。 - 创建新的子 Fiber 节点:因为是首次渲染,所有子节点也都是全新的。
- 标记为
Placement
:新创建的 DOM 节点需要在 commit 阶段被插入到 DOM 中。
updateXXX
系列函数:在current !== null
的情况下被调用。它们负责组件的更新逻辑。- 比较
props
和context
:检查是否有变化,这是进行bailout
(优化,跳过渲染) 的重要依据。 - 调用更新相关的生命周期方法:如
shouldComponentUpdate
、getDerivedStateFromProps
(类组件)。 - 复用实例:通常会复用
current
Fiber 上的组件实例 (类组件)。 diff
子节点:调用reconcileChildren
时,会比较current.child
和新的children
元素,以决定是复用、更新、移动还是删除子 Fiber。- 标记副作用:根据
diff
结果和组件逻辑,标记相应的副作用 (如Update
、Deletion
等)。
- 比较
生命周期方法的调用:
mountXXX
过程中会触发挂载相关的生命周期方法 (如componentDidMount
会在 commit 阶段被调度)。updateXXX
过程中会触发更新相关的生命周期方法 (如componentDidUpdate
会在 commit 阶段被调度)。
性能优化路径:
updateXXX
路径有更多的优化机会。最典型的就是bailout
逻辑:如果props
、context
没有变化,并且没有强制更新的标记,React 可能会直接复用current
Fiber 的子树,跳过对其子组件的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
- 如果当前
workInProgress
Fiber 有子节点,beginWork
会返回其第一个子 Fiber 节点。这个子节点将成为performUnitOfWork
中的下一个workInProgress
,继续进行深度优先遍历。 - 如果当前
workInProgress
Fiber 没有子节点(例如,一个文本节点,或者一个组件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 链表链接到
workInProgress
Fiber 上。 - 返回第一个子 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,则标记Update
flag,表明在 Commit 阶段需要更新 DOM 属性。HostText
: 如果文本内容改变,调用updateHostText
标记Update
flag。
- 对于初次渲染 (
- 处理副作用 (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
、标记Visibility
flag 等。会调用popSuspenseHandler
。SuspenseListComponent
: 处理 SuspenseList 的复杂逻辑,协调子 SuspenseComponent 的显示顺序和 fallback 状态。会调用popSuspenseListContext
。OffscreenComponent
,LegacyHiddenComponent
: 处理屏幕外或隐藏组件的逻辑,例如标记Visibility
flag,弹出SuspenseHandler
和HiddenContext
。ContextProvider
: 弹出 Context Provider。调用popProvider
。CacheComponent
: 处理缓存组件的逻辑,例如比较前后缓存是否变化,如果变化则标记Passive
flag 以执行副作用。调用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
正是连接这两个阶段,并驱动整个工作循环向前进行的关键函数。
