Skip to content

对应源码:packages/react-reconciler/src/ReactFiberCompleteWork.js

1. completeWork 的职责

completeWork 是"归"阶段的处理函数,在 completeUnitOfWork 内部调用。当 beginWork 返回 null(没有子节点)时,处理流程从"递"切换到"归"。Inside Fiber (Max Koretskyi) 对 completeUnitOfWork 的向上冒泡做了逐行解析。

javascript
function completeWork(
  current: Fiber | null,       // 上一次渲染的 Fiber
  workInProgress: Fiber,        // 本次渲染的 Fiber
  renderLanes: Lanes,
): Fiber | null

完成时执行职责:

  1. 创建真实 DOM 节点(首次渲染/mount)
  2. 准备 DOM 更新队列(更新时)
  3. 将子节点的 DOM 挂载到当前 DOM 上(appendAllChildren
  4. 收集子树的 flags 到父节点的 subtreeFlags(冒泡副作用)

2. HostComponent 的 completeWork

javascript
// packages/react-reconciler/src/ReactFiberCompleteWork.js(简化)

function completeWork(current, workInProgress, renderLanes) {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    case HostComponent: {  // <div>, <span> 等 DOM 元素
      const type = workInProgress.type;

      if (current !== null && workInProgress.stateNode != null) {
        // === 更新场景 ===
        // current 存在 && DOM 节点已创建
        updateHostComponent(current, workInProgress, type, newProps, renderLanes);
        // updateHostComponent 内部(mutation 模式):
        //   const oldProps = current.memoizedProps;
        //   if (oldProps === newProps) return;  // props 没变 → 跳过
        //   markUpdate(workInProgress);  // props 变了 → 标记 Update flag
        // 注意:props diff 不在这里做,而是在 commit 阶段的 commitUpdate 中完成
        bubbleProperties(workInProgress);
        return null;
      }

      // === 首次渲染场景 ===
      // 创建真实 DOM 节点
      const instance = createInstance(type, newProps, ...);

      // 关键:将子节点的 DOM 挂载到当前 DOM 上
      appendAllChildren(instance, workInProgress);

      // 设置 stateNode 指向真实 DOM
      workInProgress.stateNode = instance;

      // 设置初始 DOM 属性
      finalizeInitialChildren(instance, type, newProps, ...);

      // 标记 Placement
      workInProgress.flags |= Placement;

      bubbleProperties(workInProgress);
      return null;
    }

    case HostText: {  // 纯文本节点
      const newText = newProps;

      if (current !== null && workInProgress.stateNode != null) {
        // 更新:比较新旧文本
        const oldText = current.memoizedProps;
        if (oldText !== newText) {
          workInProgress.flags |= Update;
        }
        bubbleProperties(workInProgress);
        return null;
      }

      // 首次渲染:创建 TextNode
      const instance = createTextInstance(newText, ...);
      workInProgress.stateNode = instance;
      workInProgress.flags |= Placement;
      bubbleProperties(workInProgress);
      return null;
    }

    case FunctionComponent: {
      // 函数组件在 completeWork 中几乎不做事
      // 主要是 bubbleProperties(冒泡子树 flags)
      bubbleProperties(workInProgress);
      return null;
    }
  }
}

3. appendAllChildren:构建 DOM 树结构

这是 completeWork 最关键的操作之一。在首次渲染时,需要把子孙节点的 DOM 挂接到当前节点的 DOM 上,形成真实的 DOM 树。

Fiber 树 :                    DOM 树 :
App                          (还没有 DOM)
div                         <div>                ← 正在 completeWork
span                    <span>               ← 子节点 DOM 已创建
"Hi"                "Hi"                 ← 孙节点 DOM 已创建
button                  <button>             ← 子节点 DOM 已创建
completeWork(div) 调用 appendAllChildren(div_DOM):
遍历 div 的所有子孙 Fiber
把它们的 stateNode (DOM 节点) appendChild 到 div_DOM
结果 DOM 树:
<div>
<span>Hi</span>
<button>...</button>
</div>

appendAllChildren 要遍历整个子树(不只是直接子节点),因为中间可能有 Fragment 或其他不产生 DOM 节点的类型:

Fiber 树:
  div (HostComponent)
    Fragment                  ← 不产生 DOM
      span (HostComponent)    ← 产生 DOM
      span (HostComponent)    ← 产生 DOM

appendAllChildren(div_DOM):
  跳过 Fragment(不产生 DOM)
  找到 span → appendChild 到 div_DOM
  找到 span → appendChild 到 div_DOM

4. bubbleProperties:冒泡副作用

completeWork 的最后一步是 bubbleProperties——将子树的 flags 冒泡到父节点的 subtreeFlagsReact 技术揭秘 - completeWork 详细分析了 bubbleProperties 如何优化 Commit 阶段的遍历。

javascript
// 简化版
function bubbleProperties(completedWork) {
  let subtreeFlags = NoFlags;

  let child = completedWork.child;
  while (child !== null) {
    subtreeFlags |= child.flags;
    subtreeFlags |= child.subtreeFlags;

    // 注意:deletions 不在 bubbleProperties 中收集
    // deletions 在 beginWork 的 reconcileChildren 阶段通过 deleteChild 直接设置
    // (见 ReactChildFiber.js 的 deleteChild 函数 → returnFiber.deletions.push(childToDelete))

    child = child.sibling;
  }

  completedWork.subtreeFlags = subtreeFlags;
}

冒泡效果(示例):

         div
  flags: NoFlags
  subtreeFlags: Placement | Passive ← 冒泡收集
  deletions: null
     │ child              │ sibling
     ▼                    ▼
   span                 button
  flags: Placement     flags: Passive
  subtree: NoFlags      subtree: NoFlags

div.subtreeFlags = span.flags | button.flags
                 = Placement | Passive

Commit 阶段遍历到 div:
  看到 subtreeFlags 有 Passive
  → 知道子树有 useEffect 需要执行
  → 但不需要检查 div 本身(div.flags = NoFlags)

5. 首次渲染 vs 更新的区别

首次渲染(mount):
  completeWork 主要做两件事:
  1. createInstance / createTextInstance → 创建真实 DOM 节点
  2. appendAllChildren → 组装 DOM 树结构
  3. finalizeInitialChildren → 设置初始属性
  4. 标记 Placement flag(Commit 阶段插入 DOM)

更新(update):
  completeWork 主要做:
  1. 比较 oldProps === newProps → 相等则跳过
  2. 不等 → markUpdate(标记 Update flag)
  3. 实际的 props diff 在 Commit 阶段的 commitUpdate 中完成
  4. 不需要创建新 DOM(复用 stateNode)
  5. 不需要 appendAllChildren(DOM 结构已存在)

6. createInstance 的实际实现

createInstance 不是在 react-reconciler 中定义的——它由 HostConfig 注入。对 ReactDOM 来说:

javascript
// packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js(简化)

function createInstance(type, props, rootContainer, hostContext, internalHandle) {
  // 创建真实 DOM 元素
  const domElement = createElement(type, props, rootContainer);

  // 预处理特殊属性
  precacheFiberNode(internalHandle, domElement);
  updateFiberProps(domElement, props);

  return domElement;
}

function createElement(type, props, rootContainer) {
  // 最终调用 document.createElement
  const domElement = document.createElement(type);
  return domElement;
}

这就是 React 与平台解耦的关键——createInstance 在 DOM 环境下创建 HTMLElement,在 Native 下创建原生 View,在 Test Renderer 下创建 mock 对象。

下一步

参考资料

Released under the MIT License.