beginWork 与 completeWork 的内部职责分工

一、引言

1.1 从 JSX 到 DOM 挂载

初次渲染是 "无旧 DOM 可复用" 的全新构建过程,整体流程可分为三步:

  1. 初始化 Fiber 根节点:React 从 ReactDOM.createRoot 或 ReactDOM.render 触发,创建根 Fiber 节点(FiberRoot),作为整个 Fiber 树的入口;
  2. 构建 workInProgress Fiber 树:通过 beginWork 自上而下遍历,为每个组件创建对应的 workInProgress Fiber 节点;再通过 completeWork 自下而上处理,生成 DOM 节点并绑定属性;
  3. 提交 DOM 挂载:构建完 workInProgress 树后,React 进入 "提交阶段",将 workInProgress 树对应的 DOM 节点挂载到页面,同时将 current 树指向新树,完成渲染闭环。

1.2 beginWork 与 completeWork 的核心职责定位

beginWork 与 completeWork 是 Fiber 构建阶段的 "左右护法",职责分工明确且互补:

  1. beginWork:负责 "向下探索",从根 Fiber 开始,递归(迭代)为每个组件创建 workInProgress Fiber 节点,核心是 "生成 Fiber 结构、处理组件逻辑、确定子节点类型";
  2. completeWork:负责 "向上收尾",从叶子节点开始,逐步向上归并,核心是 "创建 DOM 节点、赋值 DOM 属性、收集副作用、关联父子 DOM";

二者通过 "工作单元链表" 衔接:beginWork 处理完一个 Fiber 节点后,工作循环通过 performUnitOfWork 推进到下一个 Fiber(child/sibling/return),当遍历到叶子节点后,自动进入 completeWork 阶段。

二、beginWork:Fiber 树的向下构建与任务分解

beginWork 是 Fiber 构建阶段的 "前置引擎",其核心逻辑是 "基于旧 Fiber 节点(若存在)或组件类型,创建新的 workInProgress Fiber 节点,并确定子节点的处理方式"。在初次渲染中,由于没有旧 current 树,beginWork 会直接基于组件类型初始化 Fiber 节点。

2.1 beginWork 的触发时机与入参说明

  1. 触发位置:工作循环在每个"工作单元(Fiber)"上调用 performUnitOfWork,其中第一步就是执行 beginWork(current, workInProgress, renderLanes),返回"下一个要处理的子 Fiber"。
  2. 入参与角色:
  • current:当前已提交树中的对应 Fiber(更新时存在;首屏挂载时可能为 null)。
  • workInProgress:本次渲染要构建的 Fiber 节点(WIP 树)。
  • renderLanes:当前渲染的优先级集合(决定是否跳过或继续展开)。
  1. 输出:返回子 Fiber(或 null),供工作循环继续向下深入。

2.2 不同类型 Fiber 节点的 beginWork 处理逻辑

2.2.1 重点节点速览

共性流程:根据 fiber.tag 分发到不同的更新函数,产出"下一步要渲染的子元素集合",再调用"对比/构建子 Fiber(reconcileChildren)"。

常见类型与处理要点(简化版):

  1. FunctionComponent:renderWithHooks 执行函数组件,跑 hooks,拿到 nextChildren;随后 reconcileChildren
  2. ClassComponent:updateClassComponent 处理实例与 processUpdateQueue,计算新 state,调用 render() 得到 nextChildren;随后 reconcileChildren
  3. HostRoot(根):处理根上的更新队列(如 updateContainer 的结果),得到 nextChildren;随后 reconcileChildren
  4. HostComponent(原生节点,如 div):不在此阶段做 DOM 变更;只根据 props.children 调用 reconcileChildren
  5. HostText(文本):无子节点,通常返回 null
  6. Suspense/Offscreen:根据数据可用性与可见性策略,决定展开主内容或 fallback,再 reconcileChildren
  7. Memo/ForwardRef/SimpleMemoComponent:按其包装逻辑判断是否需要重渲染,否则可部分跳过。

简化示例(伪代码):

javascript 复制代码
function beginWork(current, workInProgress, renderLanes) {
  switch (workInProgress.tag) {
    case FunctionComponent:
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps; // 未解析默认值的 props
      // 解析函数组件的默认 props(若禁用默认 props 则直接使用 unresolvedProps)
      const resolvedProps =
        disableDefaultPropsExceptForClasses ||
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultPropsOnNonClassComponent(Component, unresolvedProps);
      // 处理函数组件的更新/挂载(执行函数、处理 Hooks、生成子节点)
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes
      );

    case ClassComponent:
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps; // 未解析默认值的 props
      // 解析类组件的默认 props(static defaultProps)
      const resolvedProps = resolveClassComponentProps(
        Component,
        unresolvedProps,
        workInProgress.elementType === Component
      );
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes
      );

    case HostRoot: {
      // 处理根节点的更新/挂载(管理全局状态、初始化上下文、协调子树)
      return updateHostRoot(current, workInProgress, renderLanes);
    }

    case HostComponent: {
      // 处理 DOM 元素的更新/挂载(对比 props、生成 DOM 属性、协调子节点)
      return updateHostComponent(current, workInProgress, renderLanes);
    }

    case HostText:
      return updateHostText(current, workInProgress);

    // ... 其他类型(Suspense、Offscreen、Fragment、ContextProvider 等)
  }
}

2.2.2 updateFunctionComponent 源码精读

在函数组件的 beginWork 阶段,updateFunctionComponent 负责"调用组件函数 + 驱动 Hooks 执行 + 产出子节点 + 协调子 Fiber"。它与 renderWithHooks 搭档工作:前者决定是否需要继续向下构建子树(以及是否可以直接跳过),后者负责在一次渲染里正确地运行 Hooks 并返回最新的子节点。

执行顺序总览:

  1. 读取上下文(prepareToReadContext),为组件执行铺路;
  2. 调用 renderWithHooks,运行 Hooks 并拿到 nextChildren(JSX);
  3. 若是更新且未接收变化(current !== null && !didReceiveUpdate),走"跳过路径",复用旧子树; didReceiveUpdate 来自更上层的变更判断(如 props/context 是否变化、更新优先级是否匹配)。当它为 false 时表示"本次无需对这个节点及其子树做工作,可直接重用旧 Fiber 和副作用信息",于是会调用:
  4. 否则标记 PerformedWork 并调用 reconcileChildren,为子节点生成/更新子 Fiber。

renderWithHooks 的职责与关键点

  1. 渲染前重置当前 Fiber 的 Hooks 相关状态:memoizedStateupdateQueuelanes,并设置全局渲染环境(currentlyRenderingFiberrenderLanes)。
  2. 根据是否首屏挂载选择调度器:
  • HooksDispatcherOnMount: 首次渲染,负责初始化如 useState 的初始值。
  • HooksDispatcherOnUpdate: 更新渲染,负责读取旧状态并应用队列里的更新。
  1. 运行组件函数得到 children;如果在执行过程中触发了"渲染阶段更新"(didScheduleRenderPhaseUpdateDuringThisPass),则使用 HooksDispatcherOnRerender 进行一次或多次"立即重渲染",直到状态稳定或达到 RE_RENDER_LIMIT 上限。
  • 这类更新的典型特征是"在函数组件执行过程中触发了自身状态的再次变更",React 会尝试在同一渲染通道内收敛到稳定值,避免把不稳定的 UI 结果提交出去。
  1. 清理并完成 Hooks 的渲染(finishRenderingHooks),将最终的 Hooks 状态和副作用信息落到 workInProgress 上,并返回稳定的 children 给上层做协调。
javascript 复制代码
// 处理函数组件的更新逻辑
function updateFunctionComponent(
  current: null | Fiber, // 旧 Fiber 节点(更新时存在,初次渲染为 null)
  workInProgress: Fiber, // 当前正在构建的 Fiber 节点
  Component: any, // 函数组件本身
  nextProps: any, // 新的 props
  renderLanes: Lanes // 当前渲染的优先级
) {
  let context;
  // 获取上下文(简化处理,省略 legacy 上下文兼容逻辑)
  if (!disableLegacyContext) {
    const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
    context = getMaskedContext(workInProgress, unmaskedContext);
  }

  let nextChildren;
  // 准备读取上下文
  prepareToReadContext(workInProgress, renderLanes);
  // 执行函数组件,通过 Hooks 处理状态和副作用,获取返回的子节点(JSX)
  nextChildren = renderWithHooks(
    current,
    workInProgress,
    Component,
    nextProps,
    context,
    renderLanes
  );

  // 如果是更新且组件未发生变化,直接复用旧节点(跳过子树更新)
  if (current !== null && !didReceiveUpdate) {
    bailoutHooks(current, workInProgress, renderLanes);
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }

  // 标记组件已执行工作
  workInProgress.flags |= PerformedWork;
  // 协调子节点(创建/更新子 Fiber 树)
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  // 返回第一个子 Fiber 节点,继续构建子树
  return workInProgress.child;
}

// 函数组件核心渲染逻辑:执行组件并处理 Hooks
export function renderWithHooks(
  current: Fiber | null, // 旧 Fiber 节点(更新时存在,初次渲染为 null)
  workInProgress: Fiber, // 当前构建中的 Fiber 节点
  Component: (p: Props, arg: SecondArg) => any, // 目标函数组件
  props: Props, // 组件接收的新 props
  secondArg: SecondArg, // 额外参数(如上下文,函数组件通常为 context)
  nextRenderLanes: Lanes // 当前渲染优先级
): any {
  // 1. 初始化渲染相关全局状态
  renderLanes = nextRenderLanes; // 记录当前渲染优先级
  currentlyRenderingFiber = workInProgress; // 标记当前正在渲染的 Fiber

  // 2. 重置当前 Fiber 的 Hooks 相关状态(清空旧状态/队列)
  workInProgress.memoizedState = null; // 清空 Hooks 状态存储
  workInProgress.updateQueue = null; // 清空 Hooks 更新队列
  workInProgress.lanes = NoLanes; // 重置优先级标记

  // 3. 选择 Hooks 调度器(区分初次渲染/更新)
  // - 初次渲染(current 为 null 或无旧 Hooks 状态):用挂载阶段调度器
  // - 更新阶段(有旧 Hooks 状态):用更新阶段调度器
  ReactSharedInternals.H =
    current === null || current.memoizedState === null
      ? HooksDispatcherOnMount // 挂载时的 Hooks 实现(如 useState 初始化)
      : HooksDispatcherOnUpdate; // 更新时的 Hooks 实现(如 useState 读取旧状态)

  // 4. 执行函数组件,获取返回的子节点(JSX)
  // 此时组件内的 Hooks(如 useState、useEffect)会通过上面的调度器执行
  let children = Component(props, secondArg);

  // 5. 处理渲染阶段更新(若组件内触发了 setState 等导致状态变化)
  if (didScheduleRenderPhaseUpdateDuringThisPass) {
    // 重新执行组件,确保状态稳定后再返回子节点
    children = renderWithHooksAgain(
      workInProgress,
      Component,
      props,
      secondArg
    );
  }

  // 6. 完成 Hooks 渲染:清理全局状态、记录最终 Hooks 信息
  finishRenderingHooks(current, workInProgress, Component);

  // 7. 返回组件渲染结果(子节点),用于后续 Fiber 子树协调
  return children;
}

2.2.3 updateClassComponent 源码精读

在 beginWork 阶段,类组件通过 updateClassComponent 完成"实例构造/更新决策 + 调用 render 产出子节点 + 协调子 Fiber"。与函数组件不同,类组件不使用 Hooks;更新是否继续由 shouldUpdate 决策,来源于 mountClassInstance/resumeMountClassInstance/updateClassInstance

执行顺序总览

  1. 上下文与错误边界预处理:
  • 如果是 legacy 上下文提供者,先 pushLegacyContextProvider,避免上下文栈失配;
  1. 实例与更新决策:
  • instance === null 首次挂载:constructClassInstancemountClassInstanceshouldUpdate = true
  • current === null 恢复挂载(如挂起后恢复):resumeMountClassInstance → 返回 shouldUpdate
  • 否则常规更新:updateClassInstance → 返回 shouldUpdate
  1. 渲染与子树协调(finishClassComponent):
  • !shouldUpdate && !didCaptureError,直接走"跳过路径"并可能失效上下文提供者;
  • 否则调用 instance.render() 产出 nextChildren,根据是否处于错误恢复决定 forceUnmountCurrentAndReconcile 或常规 reconcileChildren
  • 最后将 instance.state 挂到 memoizedState 并在提供者场景下 invalidateContextProvider
javascript 复制代码
// 处理类组件的更新逻辑
function updateClassComponent(
  current: Fiber | null, // 旧 Fiber 节点(更新时存在)
  workInProgress: Fiber, // 当前构建中的 Fiber 节点
  Component: any, // 类组件本身(构造函数)
  nextProps: any, // 新的 props
  renderLanes: Lanes // 当前渲染优先级
) {
  let hasContext = false;
  // 处理上下文提供者(若为上下文提供者组件)
  if (isLegacyContextProvider(Component)) {
    hasContext = true;
    pushLegacyContextProvider(workInProgress); // 入栈上下文提供者
  }

  // 准备读取上下文
  prepareToReadContext(workInProgress, renderLanes);

  const instance = workInProgress.stateNode; // 类组件实例(this)
  let shouldUpdate; // 是否需要更新

  if (instance === null) {
    // 1. 组件未实例化(初次挂载)
    constructClassInstance(workInProgress, Component, nextProps); // 创建实例(new Component())
    mountClassInstance(workInProgress, Component, nextProps, renderLanes); // 初始化实例(调用 componentWillMount 等)
    shouldUpdate = true; // 初次挂载必更新
  } else if (current === null) {
    // 2. 恢复挂载(如从 Suspense 恢复)
    shouldUpdate = resumeMountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderLanes
    );
  } else {
    // 3. 组件已存在(更新阶段)
    // updateClassInstance通过处理新旧 props/context、执行状态更新队列、调用相关生命周期方法(如 componentWillReceiveProps、
    // shouldComponentUpdate),最终判断组件是否需要重新渲染,并更新实例的 props/state/context
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderLanes
    );
  }

  // 完成类组件处理,返回下一个工作单元
  const nextUnitOfWork = finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderLanes
  );

  return nextUnitOfWork;
}

// 完成类组件的渲染处理
function finishClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  shouldUpdate: boolean,
  hasContext: boolean,
  renderLanes: Lanes
) {
  // 处理 ref 引用
  markRef(current, workInProgress);

  // 检查是否捕获到错误
  const didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags;

  // 若无需更新且无错误捕获,直接复用旧节点(跳过子树更新)
  if (!shouldUpdate && !didCaptureError) {
    if (hasContext) {
      invalidateContextProvider(workInProgress, Component, false); // 失效上下文
    }
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }

  const instance = workInProgress.stateNode; // 类组件实例
  let nextChildren; // 渲染的子节点(JSX)

  if (didCaptureError && !Component.getDerivedStateFromError) {
    // 错误捕获且无错误处理方法时,子节点为空
    nextChildren = null;
  } else {
    // 执行 render 方法获取子节点
    nextChildren = instance.render();
  }

  // 标记组件已执行工作
  workInProgress.flags |= PerformedWork;

  // 协调子节点(创建/更新子 Fiber 树)
  if (current !== null && didCaptureError) {
    // 错误恢复场景:强制卸载旧子树并重新协调
    forceUnmountCurrentAndReconcile(
      current,
      workInProgress,
      nextChildren,
      renderLanes
    );
  } else {
    // 正常场景:协调子节点
    reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  }

  // 保存当前状态到 Fiber 节点
  workInProgress.memoizedState = instance.state;

  if (hasContext) {
    invalidateContextProvider(workInProgress, Component, true); // 更新上下文
  }

  // 返回第一个子 Fiber 节点,继续构建子树
  return workInProgress.child;
}

2.3 子 Fiber 节点的创建与链表连接

两套对比器(核心区别在"有没有旧树可复用"):

  1. 首次挂载:mountChildFibers(从 null 构建新子 Fiber 链)。
  2. 更新:reconcileChildFibers(对比 current.child 链与新的 nextChildren:复用/插入/删除)。

链表结构与指针:

  1. fiber.child:第一个子节点。
  2. fiber.sibling:兄弟节点(单链向右)。
  3. fiber.return:父节点(便于回溯完成与向上合并副作用)。
jsx 复制代码
// JSX

  
  


// Fiber 链(简化)
MyPanel (return: parent)
  └─ child → Title (return: MyPanel)
       └─ sibling → Content (return: MyPanel)

关键点:beginWork 只"产出子树的结构与下一步任务",不做 DOM;DOM 的创建与属性设置在 completeWork,插入/更新在"提交阶段"。

2.4 工作单元的分解与优先级处理

  1. 单元分解(DFS):
  • 工作循环优先深入 childchild 完成后转向 sibling;最后回到 return
  • 每个 Fiber 的 begin/complete 构成一个"工作单元",渲染可切片、可中断。
  1. 优先级(Lanes):
  • renderLanes 决定当前渲染的优先级集合;若该 Fiber 在此优先级下无待处理工作,可"跳过"(复用旧树)。
  • 出口条件(简化理解):没有更高优先级的变更、props/state 未变、更不需要上下文更新 → bailout,直接返回可能的 childnull
  1. 概念串联:
  • beginWork:决定"需要渲染哪些子元素",并产生/对齐子 Fiber。
  • completeWork:在向上回溯时创建宿主实例、收集副作用。
  • 提交阶段:统一执行副作用(commitPlacement/commitUpdate 等),把渲染结果应用到 DOM。
jsx 复制代码
// 例子:点击后新增一个 <li>
function List({items}) {
  return (
    <ul>
      {items.map((i) => (
        <li>{i}</li>
      ))}
    </ul>
  );
}

// 点击 setState 入队 → 渲染阶段:
// beginWork(List) → nextChildren = [..., <li>]
// reconcileChildren 对比老的 <li> 列表:复用旧的、创建新的 Fiber
// completeWork(new <li>):准备 DOM 实例与属性
// 提交阶段:commitPlacement(new <li>) 插入到真实 DOM

三、completeWork:Fiber 树的向上归并与 DOM 生成

当 beginWork 遍历到叶子节点(无 child 的 Fiber 节点)后,performUnitOfWork 会切换到 completeWork 阶段。completeWork 从叶子节点开始向上归并,核心是 "将 Fiber 节点转换为实际 DOM,并完成挂载前的准备"。

3.1 completeWork 的触发时机与执行条件

  1. 触发时机:工作循环在某个 Fiber 的子树已展开完毕、beginWork 返回 null 后,会回溯到该 Fiber 并执行 completeWork(current, workInProgress, renderLanes)。这是一段"向上归并"的过程。
  2. 作用概览:
  • 为宿主节点(HostComponent/HostText)创建或对齐实例(不插入 DOM)。
  • 为"更新"生成变更负载(payload),以便提交阶段执行。
  • 向上合并(bubble)子树的副作用标志(subtreeFlags),为提交阶段收敛出一条待执行的效果链。

示意(伪代码):

javascript 复制代码
function completeUnitOfWork(unitOfWork: Fiber): void {
  let completedWork: Fiber = unitOfWork;
  do {
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;
    let next;

    // 执行完成工作的核心逻辑
    next = completeWork(current, completedWork, entangledRenderLanes);

    // 若生成新工作单元,切换到新工作单元并返回
    if (next !== null) {
      workInProgress = next;
      return;
    }

    // 若有兄弟节点,切换到兄弟节点并返回
    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      workInProgress = siblingFiber;
      return;
    }

    // 否则返回父节点继续处理
    completedWork = returnFiber;
    workInProgress = completedWork;
  } while (completedWork !== null);
}

3.2 不同类型 Fiber 节点的 completeWork 处理逻辑

与 beginWork 类似,completeWork 也会根据 Fiber 节点的 tag 执行差异化逻辑,核心类型如下:

  1. HostComponent(如 div):
  • 首次挂载:创建宿主实例 createInstance(type, props, rootContainer, hostContext)appendAllChildren(instance, wip) 把已创建的子实例挂到该实例上(仍在内存结构里),然后 setInitialProperties(instance, props) 设置初始属性。
  • 更新:对比新旧 props,prepareUpdate(instance, type, oldProps, newProps, rootContainer, hostContext) 返回变更负载;若非空,给 Fiber 标记 Update 并挂载负载,留给提交阶段执行。
  1. HostText(文本节点):
  • 首次挂载:createTextInstance(text, rootContainer, hostContext) 创建文本实例,挂到 fiber.stateNode
  • 更新:若文本变了,标记 Update,提交阶段会调用 commitHostTextUpdate 实际更新内容。
  1. FunctionComponent / Fragment / Context 等非宿主节点:
  • 不创建 DOM 实例;completeWork 主要做"副作用合并"和 Ref 变更判断(若 ref 变化会标记 Ref)。
  1. ClassComponent:
  • 不直接创建 DOM;常在 completeWork 阶段标记 Ref(实例 ref 变化)与合并子树效果。
  1. Suspense / Offscreen:
  • 根据可见性/回退策略在 begin 阶段已决定子树展开;complete 阶段继续合并 flags,并可能标记 Visibility 相关效果。

3.3 DOM 节点的创建、属性赋值与子节点挂载

核心要点:

  • completeWork"生成/准备"宿主实例与属性,不做真实 DOM 插入;真实插入发生在提交阶段的 commitPlacement
  • 子节点挂载通过 appendAllChildren(instance, wip) 把子实例树挂到当前实例的内部结构(即 stateNode 的子列表),为后续 commitPlacement 一次性插入打好基础。
javascript 复制代码
// (挂载 vs 更新)
function completeWork(current, wip) {
  switch (wip.tag) {
    case ActivityComponent:
    case LazyComponent:
    case SimpleMemoComponent:
    case FunctionComponent:
    case ForwardRef:
    case Fragment:
    case Mode:
    case Profiler:
    case ContextConsumer:
    case MemoComponent:
      // 这些类型的组件不需要创建DOM节点
      // 冒泡当前节点的属性(如 subtreeFlags、lanes)到父节点
      bubbleProperties(workInProgress);
      return null; // 无后续处理,返回 null
    case HostComponent: {
      // ...首次客户端渲染逻辑

      // 更新宿主容器(如 DOM 容器)的状态
      updateHostContainer(current, workInProgress);
      // 冒泡属性到父节点(根节点无父节点,此处主要是处理自身 subtreeFlags)
      bubbleProperties(workInProgress);
      return;
    }

    case HostText: {
      const newText = wip.pendingProps;
      if (current && workInProgress.stateNode != null) {
        const oldText = current.memoizedProps; // 上一次渲染的文本内容
        // 调用文本节点更新逻辑(对比新旧文本,生成更新队列)
        updateHostText(current, workInProgress, oldText, newText);
      } else {
        // 客户端挂载:创建文本实例(如 DOM 中的 Text 节点)
        const rootContainerInstance = getRootHostContainer();
        const currentHostContext = getHostContext();
        markCloned(workInProgress);
        // 创建文本实例(如 DOM 中的 Text 节点)
        workInProgress.stateNode = createTextInstance(
          newText,
          rootContainerInstance,
          currentHostContext,
          workInProgress
        );
      }
      bubbleProperties(workInProgress);
      return;
    }

    // ... 其他类型以"合并副作用"为主
  }
}

3.4 副作用的收集与标记

Flags(部分常见):

  1. Placement:表示需要插入(新增节点)。
  2. Update:属性或文本变更,提交阶段读取 Fiber 的变更负载执行。
  3. Deletion:删除节点。
  4. Ref:ref 变更,提交阶段会附加/清理 ref。
  5. Visibility / Passive:可见性与副作用相关标记。 合并与传播:
  6. 每个 Fiber 在 complete 阶段会把自身与子树的 flags 合并到 subtreeFlags,以便根在提交阶段一次性遍历需要执行的效果。

合并示意:

javascript 复制代码
// 功能:在 Fiber 树归并阶段,向上汇总子节点的优先级、副作用等关键信息
function bubbleProperties(completedWork: Fiber) {
  // 判断是否&#34;跳过更新&#34;(子树无变化):存在旧节点且新旧子节点引用相同
  const didBailout =
    completedWork.alternate !== null &&
    completedWork.alternate.child === completedWork.child;

  let newChildLanes: Lanes = NoLanes; // 合并后的子树优先级
  let subtreeFlags = NoFlags; // 合并后的子树副作用标记

  if (!didBailout) {
    // 【子树有更新】:全量收集子节点信息
    let child = completedWork.child;
    while (child !== null) {
      // 合并子节点自身及子树的优先级
      newChildLanes = mergeLanes(
        newChildLanes,
        mergeLanes(child.lanes, child.childLanes)
      );
      // 合并子节点自身及子树的所有副作用
      subtreeFlags |= child.subtreeFlags | child.flags;
      // 修正子节点的父引用,确保链表关系正确
      child.return = completedWork;

      child = child.sibling; // 遍历下一个兄弟节点
    }

    // 将合并的子树副作用标记到当前节点
    completedWork.subtreeFlags |= subtreeFlags;
  } else {
    // 【子树无更新】:仅收集静态信息(过滤动态副作用)
    let child = completedWork.child;
    while (child !== null) {
      // 合并子节点自身及子树的优先级(未处理的优先级仍需传递)
      newChildLanes = mergeLanes(
        newChildLanes,
        mergeLanes(child.lanes, child.childLanes)
      );
      // 仅合并静态副作用(如静态DOM属性,动态副作用无需传递)
      subtreeFlags |=
        (child.subtreeFlags & StaticMask) | (child.flags & StaticMask);
      // 修正子节点的父引用
      child.return = completedWork;

      child = child.sibling;
    }

    completedWork.subtreeFlags |= subtreeFlags;
  }

  // 保存合并后的子树优先级到当前节点
  completedWork.childLanes = newChildLanes;

  // 返回是否跳过更新的标志,供上层节点判断
  return didBailout;
}
  • 例子:文本更新的标记与提交
jsx 复制代码
function TextExample() {
  const [text, setText] = useState(&#34;hello&#34;);
  useEffect(() => {
    setTimeout(() => setText(&#34;world&#34;), 100);
  }, []);
  return <div>{text}</div>;
}
// 1. 初次挂载:生成 `HostText(&#34;hello&#34;)`,提交后运行 `useEffect`(被动副作用)。
// 2. 更新调度:`setTimeout` 触发 `setText(&#34;world&#34;)`,通过 `scheduleUpdateOnFiber` 为根打上待处理的 `Lane` 并触发下一次渲染。
// 3. render 阶段:
// - `BeginWork` 进入 `HostText` 的更新路径。
// - `CompleteWork` 的 `case HostText` 中调用 `updateHostText(current, workInProgress, oldText, newText)`。
// - 若 `oldText !== newText`,执行 `markUpdate(workInProgress)`,在该 Fiber 的 `flags` 中设置 `Update`。
// - 随后 `bubbleProperties` 汇总子树标志到父节点的 `subtreeFlags`,便于提交阶段快速定位变化。
// 4. 提交阶段(mutation effects):
// - 扫描到该 `HostText` 带有 `Update`,调用 `commitHostTextUpdate(stateNode, oldText, newText)` 实际更新 DOM 文本为 &#34;world&#34;。

结论(把 complete 与提交阶段区分清楚)

  1. completeWork 是"向上归并与准备":创建/对齐宿主实例、设置初始属性或生成变更负载、合并副作用标记。
  2. 真实 DOM 变更(插入/更新/删除)在提交阶段完成:如 commitPlacementcommitHostTextUpdate 等。
  3. 这保证渲染阶段能被时间切片与优先级打断,而提交阶段在确定的效果列表上一次性执行,提升稳定性与可控性。

四、beginWork 与 completeWork 的协同机制

beginWork 的 "向下构建" 与 completeWork 的 "向上归并" 并非独立流程,而是通过 "工作单元链表" 和 "双缓存机制" 紧密协同,共同完成初次渲染的 Fiber 树构建与 DOM 生成。

4.1 自上而下构建与自下而上归并的流程闭环

流程主线:

  1. 向下构建(beginWork):根据 fiber.tag 选择处理器,计算 nextChildren,并通过 mountChildFibers/reconcileChildFibers 生成/复用子 Fiber 链,返回 fiber.child 继续深入。
  2. 向上归并(completeWork):当子树展开完毕(beginWork 返回 null)回溯到父节点,创建/对齐宿主实例(HostComponent/HostText)、准备更新负载,并合并副作用标记(bubbleProperties)。
  3. 提交阶段(commit):在统一的效果列表上执行宿主副作用,将变更应用到 DOM(如 commitPlacementcommitHostTextUpdate)。
javascript 复制代码
// 简化伪码
function performUnitOfWork(wip) {
  // 向下:展开当前节点的子树
  const next = beginWork(wip.alternate, wip, renderLanes);
  wip.memoizedProps = wip.pendingProps;

  // 有子,继续向下;无子,开始向上
  if (next !== null) return next;

  // 向上:准备实例/负载并合并副作用
  do {
    completeWork(wip.alternate, wip, renderLanes);
    bubbleProperties(wip); // 合并 flags / subtreeFlags
    if (wip.sibling !== null) return wip.sibling;
    wip = wip.return;
  } while (wip !== null);

  // 根收敛后,进入提交阶段
  return null;
}

4.2 双缓存 Fiber 树切换中的职责配合

  1. 双缓存(current ↔ workInProgress):
  • 渲染阶段构建的是 WIP 树(指针 workInProgress.alternate === current);旧树 current 仍代表"已提交"的 UI。
  • commit 阶段将 root.current 切换为本次完成的 finishedWork(WIP 树),使其成为新的"已提交"树。
  1. 职责分工:
  • beginWork:读取 current 的已有信息(如 memoizedProps/state),决定是否复用(bailout)或生成新的子 Fiber。
  • completeWork:在 WIP 节点上创建/对齐宿主实例、设置初始属性或生成更新负载,并合并副作用。
  • commit:读取 WIP 树上的 Flags/负载,进行 DOM 插入/更新/删除,并完成 ref 和 effect 的附加/清理。
  1. 插入位置明确:
  • 宿主实例的"创建与属性准备"在 complete 阶段完成;
  • 真实插入 DOM 的操作在提交阶段 commitPlacement 执行。

五、最后

Fiber 架构下的渲染,本质是通过 beginWork 与 completeWork 的双向协作,完成从组件逻辑到 DOM 实例的高效转化 ------ 二者如同 "分工明确的工程队":beginWork 负责 "规划蓝图"(构建 Fiber 树结构、分解渲染任务、处理组件逻辑),completeWork 负责 "落地施工"(创建 DOM 实例、准备属性与更新负载、收集副作用),最终通过双缓存机制与提交阶段,实现视图的稳定挂载。 理解 beginWork 与 completeWork 的核心分工后,可进一步拆解 React 渲染的关键细节,形成完整的知识体系:

  1. 子 Fiber 构建的核心:reconcileChildren 逻辑:深入 mountChildFibers(初次挂载)与 reconcileChildFibers(更新)的内部实现,理解 React 如何通过 "key 比对""类型判断" 实现子节点的复用、插入与删除,掌握 Diff 算法的核心规则;
  2. 宿主节点的差异化处理:聚焦 HostComponent(原生 DOM 节点)与 HostText(文本节点)的完整生命周期,包括 createInstance/createTextInstance 的 DOM 创建细节、setInitialProperties 的属性初始化逻辑,以及更新时 prepareUpdate 的变更计算规则;
  3. 提交阶段的副作用执行:从 completeWork 收集的副作用标记出发,学习提交阶段如何通过 commitMutationEffects(执行 DOM 变更)、commitLayoutEffects(执行布局相关副作用)等流程,将 workInProgress 树的准备结果最终应用到页面;
相关推荐
3秒一个大2 小时前
从后端模板到响应式驱动:界面开发的演进之路
前端·后端
三喵2232 小时前
跨域 iframe 内嵌的同源策略适配方案-Youtube举例
前端·爬虫
灰灰勇闯IT2 小时前
RN跨端适配与沉浸式体验:适配不同设备与系统
javascript·react native·react.js
无敌暴龙兽2 小时前
Android 布局多次测量
前端
wordbaby2 小时前
React Native 数据同步双重奏:深度解析“页面级聚焦”与“应用级聚焦”的区别
前端·react native
Nayana2 小时前
Node.js中常用的异步编程方法
前端
周帝2 小时前
手写一个最简单版本的canvas库
前端
AAA简单玩转程序设计2 小时前
谁说Java枚举只是“常量装盒”?它藏着这些骚操作
java·前端
前端小蜗2 小时前
💰该省省,该花花!我靠白嫖飞书,把“每日生存成本”打了下来
前端·程序员·产品