Skip to content

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 函数的核心逻辑可以分为以下几个步骤:

核心逻辑

  1. Bailout (优化) 检查

    • 属性或上下文变化检测:如果当前 Fiber 节点存在(即不是新挂载的节点),它会比较 current.memoizedPropsworkInProgress.pendingProps,以及检查 hasLegacyContextChanged()。如果属性或旧版上下文发生变化,didReceiveUpdate 标志会被设置为 true
    • 调度更新或上下文变化检查:即使属性没有变化,也会检查是否有调度更新或上下文变化(通过 checkScheduledUpdateOrContext)。如果既没有调度更新也没有上下文变化,并且没有捕获到错误(DidCapture 标志),则会尝试提前退出(bailout),跳过当前 Fiber 及其子树的协调工作。
    • 强制更新:对于某些特殊情况,如 ForceUpdateForLegacySuspense 标志,会强制进行更新。
  2. 处理新挂载的 Fiber 节点(current === null

    • 对于新挂载的 Fiber 节点,didReceiveUpdate 标志始终为 false
    • 在水合(hydration)模式下,如果当前 Fiber 是分叉子节点(forked child),会处理 treeId 的生成。
  3. 清除待处理更新优先级:在进入 beginWork 阶段之前,workInProgress.lanes 会被设置为 NoLanes,表示当前 Fiber 节点没有待处理的更新优先级。

  4. 根据 Fiber 类型分发处理:这是 beginWork 函数最核心的部分,它使用 switch (workInProgress.tag) 语句来根据 Fiber 节点的类型(tag)调用不同的更新或挂载函数。以下是一些主要 Fiber 类型的处理方式:

    • LazyComponent:调用 mountLazyComponentupdateLazyComponent (取决于 current 是否为 null) 来处理懒加载组件。它会尝试解析 LazyComponent,如果组件已经加载,则会像处理 FunctionComponentClassComponent 一样处理它。
    • FunctionComponent:调用 updateFunctionComponent 来处理函数组件。这包括执行函数组件本身、处理 hooks,并对其子节点进行 reconcile
    • ClassComponent:调用 updateClassComponent 来处理类组件。这包括实例化组件、调用生命周期方法(如 shouldComponentUpdategetDerivedStateFromPropsrender),并对其子节点进行 reconcile
    • HostRoot:调用 updateHostRoot 来处理根 Fiber 节点。这通常涉及到处理根节点的 children,并设置必要的上下文信息。
    • HostComponent:调用 updateHostComponent 来处理原生 DOM 元素对应的 Fiber 节点(如 <div>, <p>)。它主要负责处理该元素的 children
    • HostText:调用 updateHostText 来处理文本节点。文本节点没有 children,所以此函数通常返回 null
    • HostPortal:调用 updateHostPortal 来处理 Portal。它会处理 Portalchildren,但这些 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 来处理使用旧版 contextTypeContext.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 的组件。
  5. 错误处理:如果遇到未知的 Fiber 节点类型,beginWork 会抛出一个错误。

mountXXXupdateXXX函数的区别

beginWork 阶段,React 通过检查 current Fiber 是否为 null 来判断是挂载还是更新。基于这个判断,它会调用相应的 mountXXXupdateXXX 函数变体 (或者在同一个 updateXXX 函数内部通过 if (current === null) 来区分逻辑分支)。这种区分的核心原因在于 组件生命周期的不同阶段及其对应的特定逻辑和优化机会。要知道beginWork 的主要职责之一就是判断当前是在处理一个全新的节点(挂载)还是一个已存在的节点(更新)。

current Fiber 的作用
  • 当一个 Fiber 节点首次被创建和渲染时,它对应的 current Fiber 是 null。这意味着这个节点在屏幕上还没有对应的已提交版本。
  • 当一个 Fiber 节点发生更新时,它会有一个非 nullcurrent Fiber,这个 current Fiber 代表了该节点在上一次成功渲染并提交到屏幕上的状态。
mountXXXupdateXXX 的区别
  1. 初始化 vs. 比较与复用

    • mountXXX 系列函数:在 current === null 的情况下被调用。它们负责组件的首次初始化
      • 创建实例:对于类组件,需要创建新的组件实例。
      • 执行构造函数和初始渲染:调用 constructor (类组件),执行函数组件体。
      • 设置初始 stateprops
      • 创建新的子 Fiber 节点:因为是首次渲染,所有子节点也都是全新的。
      • 标记为 Placement:新创建的 DOM 节点需要在 commit 阶段被插入到 DOM 中。
    • updateXXX 系列函数:在 current !== null 的情况下被调用。它们负责组件的更新逻辑
      • 比较 propscontext:检查是否有变化,这是进行 bailout (优化,跳过渲染) 的重要依据。
      • 调用更新相关的生命周期方法:如 shouldComponentUpdategetDerivedStateFromProps (类组件)。
      • 复用实例:通常会复用 current Fiber 上的组件实例 (类组件)。
      • diff 子节点:调用 reconcileChildren 时,会比较 current.child 和新的 children 元素,以决定是复用、更新、移动还是删除子 Fiber。
      • 标记副作用:根据 diff 结果和组件逻辑,标记相应的副作用 (如 UpdateDeletion 等)。
  2. 生命周期方法的调用

    • mountXXX 过程中会触发挂载相关的生命周期方法 (如 componentDidMount 会在 commit 阶段被调度)。
    • updateXXX 过程中会触发更新相关的生命周期方法 (如 componentDidUpdate 会在 commit 阶段被调度)。
  3. 性能优化路径

    • updateXXX 路径有更多的优化机会。最典型的就是 bailout 逻辑:如果 propscontext 没有变化,并且没有强制更新的标记,React 可能会直接复用 current Fiber 的子树,跳过对其子组件的 beginWorkcompleteWork 阶段,从而显著提升性能。
    • mountXXX 路径通常没有这样的 bailout 机会,因为它必须完成组件的初始化和首次渲染。
  4. Hooks 的行为

    • 对于函数组件,Hooks (如 useState, useEffect) 在挂载和更新时的行为也是不同的。
      • useState 的初始值在挂载时使用。
      • useEffect 的回调函数在挂载后执行 (如果依赖数组为空或未提供),其清理函数在组件卸载前执行。在更新时,如果依赖项发生变化,则先执行上一次的清理函数,再执行新的回调函数。 这些差异也是在 mountXXXupdateXXX (具体来说是 renderWithHooks 内部) 中处理的。

这种区分使得 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。

核心内部步骤:

  1. 初始化全局 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; 等。
  2. 选择 Hooks Dispatcher

    • ReactSharedInternals.H (全局 Hooks Dispatcher 对象) 会被设置为:
      • 如果 current === null 或者 current.memoizedState === null (表示是 Mount 阶段,或者 Update 阶段但之前没有状态型 Hook),则设置为 HooksDispatcherOnMount
      • 否则 (表示是 Update 阶段且之前有状态型 Hook),则设置为 HooksDispatcherOnUpdate
    • HooksDispatcherOnMountHooksDispatcherOnUpdate 包含了各个 Hook API (如 useState, useEffect) 在 Mount 和 Update 阶段的不同实现。
  3. 执行组件函数

    • let children = Component(props, secondArg);
    • 在这一步,组件内部调用的 useState, useEffect 等会通过 ReactSharedInternals.H 指向的 Dispatcher 执行。
      • Mount 时,Hook 会创建新的 Hook 对象并加入到 workInProgress.memoizedState 链表中。
      • Update 时,Hook 会从 current.memoizedState 中找到对应的旧 Hook 对象,计算新状态,并更新 workInProgress.memoizedState
  4. 处理渲染阶段的更新 (Render Phase Updates)

    • 检查 didScheduleRenderPhaseUpdateDuringThisPass 标志位(这个标志位在 Hook 的 state 更新函数被调用时,如果是在渲染阶段,则会被设为 true)。
    • 如果为 true,说明在本次组件渲染过程中,有 Hook 触发了状态更新,需要重新渲染组件以获取最新的状态。
    • 此时会调用 renderWithHooksAgain(workInProgress, Component, props, secondArg); 来重新执行组件的渲染逻辑。这个过程可能会重复,直到没有新的渲染阶段更新(组件状态稳定)。
  5. 完成 Hooks 渲染并清理

    • 调用 finishRenderingHooks(current, workInProgress, Component);
      • ReactSharedInternals.H 重置为 ContextOnlyDispatcher (一个只包含 readContext 的 dispatcher,防止在渲染后意外调用其他 Hooks) 这是一个关键步骤,它确保在组件渲染逻辑之外(例如,在 useEffect 的清理函数或异步回调中)不能再调用除 useContext (通过 readContext) 之外的 Hooks,否则会报错。这有助于维持 Hooks 调用的规则(只能在顶层和自定义 Hook 中调用)
      • 重置全局 Hooks 状态变量,如 renderLanes, currentlyRenderingFiber, currentHook, workInProgressHook, didScheduleRenderPhaseUpdate 等。
  6. 返回渲染结果

    • 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 的内部步骤可以概括为:

  1. 识别 Mount 或 Update 场景。
  2. 如果是 Mount:调用 mountChildFibers,它通过 reconcileChildFibersImpl (以 shouldTrackSideEffects = false 的模式) 创建所有新的子 Fiber,并标记为 Placement
  3. 如果是 Update:调用 reconcileChildFibers,它通过 reconcileChildFibersImpl (以 shouldTrackSideEffects = true 的模式) 对比新旧子节点,执行 Diff 算法,复用、创建、更新或删除子 Fiber,并标记相应的副作用 (Placement, Update, Deletion)。
  4. reconcileChildFibersImpl 根据 newChild 的具体类型(单一元素、数组、文本等)分发到更具体的协调函数进行处理。
  5. 将协调产生的子 Fiber 链表链接到 workInProgress Fiber 上。
  6. 返回第一个子 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 节点的工作。

它的核心职责包括:

  1. 创建/更新 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。
  2. 处理副作用 (Side Effects): 根据 beginWork 阶段和当前 completeWork 阶段的工作,标记 Fiber 节点的 flags (例如 Placement, Update, Deletion, Hydrating 等)。这些 flags 会在 Commit 阶段被用来执行实际的 DOM 操作或其他副作用。
  3. 冒泡属性 (Bubble Properties): 调用 bubbleProperties 函数,将子 Fiber 的 lanessubtreeFlags 冒泡到当前 Fiber。subtreeFlags 用于快速判断子树中是否存在需要处理的副作用,避免不必要的遍历。
  4. 处理特定的组件类型:
    • FunctionComponent, MemoComponent, SimpleMemoComponent, ForwardRef: 通常没有与 DOM 直接相关的 completeWork 逻辑,主要是 bubbleProperties
    • ClassComponent: 类似于函数组件,主要进行 bubbleProperties。如果组件是旧版的 Context Provider,会调用 popLegacyContext
    • HostRoot: 对于根节点,如果是 Mutation 模式,会调用 updateHostContainer;如果是 Hydration 模式,会处理 Hydration 相关逻辑。最终会 popHostContainerpopTopLevelLegacyContextObject
    • HostPortal: 弹出 HostContainer 上下文,并调用 updateHostContainer
    • SuspenseComponent: 处理 Suspense 边界的完成逻辑,包括判断是否超时、是否需要显示 fallback、处理 retryQueue、标记 Visibility flag 等。会调用 popSuspenseHandler
    • SuspenseListComponent: 处理 SuspenseList 的复杂逻辑,协调子 SuspenseComponent 的显示顺序和 fallback 状态。会调用 popSuspenseListContext
    • OffscreenComponent, LegacyHiddenComponent: 处理屏幕外或隐藏组件的逻辑,例如标记 Visibility flag,弹出 SuspenseHandlerHiddenContext
    • ContextProvider: 弹出 Context Provider。调用 popProvider
    • CacheComponent: 处理缓存组件的逻辑,例如比较前后缓存是否变化,如果变化则标记 Passive flag 以执行副作用。调用 popCacheProvider
    • ScopeComponent: 如果启用了 Scope API,会创建或更新 Scope 实例,并标记 Update (如果存在 ref)。
    • TracingMarkerComponent: 如果启用了 Transition Tracing,会弹出 MarkerInstance
  5. 返回下一个工作单元: completeWork 通常返回 null,表示当前 Fiber 的工作完成,控制权交还给 workLoopworkLoop 会继续处理当前 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 正是连接这两个阶段,并驱动整个工作循环向前进行的关键函数。


微信公众号二维码