第 47 期 - React 中 render 阶段 completeWork 函数解析
logoFRONTALK AI/12月9日 16:31/阅读原文

摘要

本文聚焦 React 框架 render 阶段的 completeWork 函数,阐述不同 fiber.tag 处理逻辑、mount 和 update 时的 props 处理、DOM 节点操作,还介绍了 effectList 的作用,最后提及渲染阶段结束后的流程走向。

一、整体流程背景

在 React 组件的 render 阶段包含 beginWork 与 completeWork。之前了解到 beginWork 会创建子 Fiber 节点,这部分可能存在 effectTag。现在重点关注 completeWork 的工作内容。

二、completeWork 函数基本结构

completeWork函数接收currentworkInProgressrenderLanes作为参数。它会根据workInProgress.tag的不同调用不同处理逻辑。

function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const newProps = workInProgress.pendingProps;
  switch (workInProgress.tag) {
    case IndeterminateComponent:
    case LazyComponent:
    case SimpleMemoComponent:
    case FunctionComponent:
    case ForwardRef:
    case Fragment:
    case Mode:
    case Profiler:
    case ContextConsumer:
    case MemoComponent:
      return null;
    case ClassComponent: {
      //...省略
      return null;
    }
    case HostRoot: {
      //...省略
      updateHostContainer(workInProgress);
      return null;
    }
    case HostComponent: {
      //...省略
      return null;
    }
  //...省略

三、重点关注 HostComponent

(一)处理逻辑判断依据

对于HostComponent(原生 DOM 组件对应的 Fiber 节点),和beginWork一样,根据current === null判断是mount还是update。并且针对HostComponent在判断update时,还需考虑workInProgress.stateNode!= null(即该 Fiber 节点是否存在对应的 DOM 节点)。

case HostComponent: {
  popHostContext(workInProgress);
  const rootContainerInstance = getRootHostContainer();
  const type = workInProgress.type;
  if (current!== null && workInProgress.stateNode!= null) {
    // update 的情况
    //...省略
  } else {
    // mount 的情况
    //...省略
  }
  return null;
}

(二)update 时的操作

  1. 主要操作内容update时,Fiber 节点已存在对应 DOM 节点,无需生成 DOM 节点,主要处理props。例如注册onClickonChange等回调函数,处理style propDANGEROUSLYSETINNERHTML propdangerouslysetinnerhtml)、children prop等。
  2. updateHostComponent 方法 最主要逻辑是调用updateHostComponent方法。在updateHostComponent内部,被处理完的props会被赋值给workInProgress.updateQueue,并最终在commit阶段渲染到页面上(updatePayload为数组形式,偶数索引为变化的prop key,奇数索引为变化的prop value)。
if (current!== null && workInProgress.stateNode!= null) {
  // update 的情况
  updateHostComponent(
    current,
    workInProgress,
    type,
    newProps,
    rootContainerInstance
  );
}

(三)mount 时的操作

  1. 主要操作内容 mount时主要有三个逻辑:为Fiber节点生成对应的DOM节点;将子孙DOM节点插入刚生成的DOM节点中;进行与update逻辑中的updateHostComponent类似的props处理过程。
  2. 具体代码实现
// mount 的情况
//...省略服务端渲染相关逻辑
const currentHostContext = getHostContext();
// 为 fiber 创建对应 DOM 节点
const instance = createInstance(
  type,
  newProps,
  rootContainerInstance,
  currentHostContext,
  workInProgress
);
// 将子孙 DOM 节点插入刚生成的 DOM 节点中
appendAllChildren(instance, workInProgress, false, false);
// DOM 节点赋值给 fiber.stateNode
workInProgress.stateNode = instance;
// 与 update 逻辑中的 updateHostComponent 类似的处理 props 的过程
if (
  finalizeInitialChildren(
    instance,
    type,
    newProps,
    rootContainerInstance,
    currentHostContext
  )
) {
  markUpdate(workInProgress);
}

因为completeWork属于“归”阶段调用的函数,每次调用appendAllChildren都会将已生成的子孙 DOM 节点插入当前生成的 DOM 节点下。当“归”到rootFiber时,就有一个构建好的离屏 DOM 树。而mount时只会在rootFiber存在Placement effectTagcommit阶段可通过一次插入 DOM 操作(对应一个Placement effectTag)将整棵 DOM 树插入页面。

四、effectList 相关

  1. 作用 commit阶段需要依据effectList找到所有有effectTagFiber节点并执行相应操作。
  2. 构建过程 为避免commit阶段遍历Fiber树寻找effectTag!== nullFiber节点的低效操作,在completeWork的上层函数completeUnitOfWork中,每个执行完completeWork且存在effectTagFiber节点会被保存在effectList单向链表中。effectList中第一个Fiber节点保存在fiber.firstEffect,最后一个元素保存在fiber.lastEffect。在“归”阶段,所有有effectTagFiber节点都会被追加在effectList中,最终形成以rootFiber.firstEffect为起点的单向链表。

五、流程结尾

渲染阶段全部完成后,在performSyncWorkOnRoot函数中fiberRootNode被传递给commitRoot方法,开启commit阶段工作流程。

commitRoot(root);
 

扩展阅读

Made by 捣鼓键盘的小麦 / © 2025 Front Talk 版权所有