Skip to content

鼓励作者:欢迎 star 或打赏犒劳

completeWork “归” 阶段

performUnitOfWork 函数每次会调用 beginWork 来创建当前节点的子节点,如果当前节点没有子节点,则说明当前节点是一个叶子节点。在前面我们已经知道,当遍历到叶子节点时说明当前节点 “递”阶段 的工作已经完成,接下来就要进入 “归” 阶段 ,即通过 completeUnitOfWork 执行当前节点对应的 completeWork 逻辑

completeUnitOfWork 流程图

completeUnitOfWork

completeUnitOfWork

completeUnitOfWork 函数的作用是执行当前 Fiber 节点的 completeWork 逻辑,然后将 workInProgress 赋值为当前节点的兄弟节点或父节点

源码地址 completeUnitOfWork | react-reconciler/src/ReactFiberWorkLoop.old.js

ts
function completeUnitOfWork(unitOfWork: Fiber): void {
  let completedWork = unitOfWork
  do {
    // 当前 Fiber 节点的 alternate 属性指向上一次渲染的 Fiber 节点
    const current = completedWork.alternate
    // 父节点
    const returnFiber = completedWork.return

    // 检查工作是否完成或抛出了错误
    if ((completedWork.flags & Incomplete) === NoFlags) {
      setCurrentDebugFiberInDEV(completedWork)
      let next
      // 是否启用了 Profiler
      if (!enableProfilerTimer || (completedWork.mode & ProfileMode) === NoMode) {
        // 执行 completeWork 函数
        next = completeWork(current, completedWork, subtreeRenderLanes)
      } else {
        startProfilerTimer(completedWork)
        next = completeWork(current, completedWork, subtreeRenderLanes)
        // Update render duration assuming we didn't error.
        stopProfilerTimerIfRunningAndRecordDelta(completedWork, false)
      }
      resetCurrentDebugFiberInDEV()

      if (next !== null) {
        // Completing this fiber spawned new work. Work on that next.
        workInProgress = next
        return
      }
    } else {
      // ... 省略大量代码
    }

    const siblingFiber = completedWork.sibling
    //  如果存在兄弟节点,则将 workInProgress 赋值为当前节点的兄弟节点
    if (siblingFiber !== null) {
      workInProgress = siblingFiber
      // return 以后会继续执行 performUnitOfWork 函数,然后进入兄弟节点的 beginWork 阶段
      return
    }
    // 若兄弟节点不存在,则说明当前节点的子节点已经遍历完毕,需要返回到父节点
    completedWork = returnFiber
    // 将 workInProgress 赋值为父节点
    workInProgress = completedWork
  } while (completedWork !== null)

  // We've reached the root.
  if (workInProgressRootExitStatus === RootInProgress) {
    workInProgressRootExitStatus = RootCompleted
  }
}

completeWork

类似 beginWorkcompleteWork 也是根据 fiber.tag 来调用不同的处理逻辑(重点分析 HostComponent 的逻辑)

源码地址 completeWork | react-reconciler/src/ReactFiberCompleteWork.old.js

ts
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:
    case ClassComponent: {
      // ... 省略大量代码
      return null
    }
    case HostRoot: {
      // ... 省略大量代码
      updateHostContainer(current, workInProgress)
      return null
    }
    // 普通 DOM 标签(重点分析)
    case HostComponent: {
      popHostContext(workInProgress)
      const rootContainerInstance = getRootHostContainer()
      const type = workInProgress.type

      // 通过判断 current 来区分 mount 还是 update
      if (current !== null && workInProgress.stateNode != null) {
        // update 阶段
        updateHostComponent(current, workInProgress, type, newProps, rootContainerInstance)

        if (current.ref !== workInProgress.ref) {
          markRef(workInProgress)
        }
      } else {
        // mount 阶段
        if (!newProps) {
          if (workInProgress.stateNode === null) {
            throw new Error(
              'We must have new props for new mounts. This error is likely ' +
                'caused by a bug in React. Please file an issue.',
            )
          }

          // This can happen when we abort work.
          bubbleProperties(workInProgress)
          return null
        }

        const currentHostContext = getHostContext()

        // 判断是不是服务端渲染
        const wasHydrated = popHydrationState(workInProgress)
        if (wasHydrated) {
          if (
            prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)
          ) {
            // If changes to the hydrated node need to be applied at the
            // commit-phase we mark this as such.
            markUpdate(workInProgress)
          }
        } else {
          // 为 Fiber 节点创建对应的 DOM 节点
          const instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          )

          // 将子孙 DOM 节点插入刚生成的 DOM 节点中
          appendAllChildren(instance, workInProgress, false, false)

          // 将 DOM 节点赋值给 fiber.stateNode
          workInProgress.stateNode = instance

          if (
            // 为 DOM 节点添加属性
            finalizeInitialChildren(
              instance,
              type,
              newProps,
              rootContainerInstance,
              currentHostContext,
            )
          ) {
            markUpdate(workInProgress)
          }
        }

        if (workInProgress.ref !== null) {
          // If there is a ref on a host node we need to schedule a callback
          markRef(workInProgress)
        }
      }
      bubbleProperties(workInProgress)
      return null
    }

    // ... 以下 case 省略大量代码
    case HostText:
    case SuspenseComponent:
    case HostPortal:
    case ContextProvider:
    case IncompleteClassComponent:
    case SuspenseListComponent:
    case ScopeComponent:
    case OffscreenComponent:
    case LegacyHiddenComponent:
    case CacheComponent:
    case TracingMarkerComponent:
      return null
  }

  throw new Error(
    `Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
      'React. Please file an issue.',
  )
}

bubbleProperties 函数的作用

bubbleProperties 函数通过检查 fiber.child 及其兄弟节点 fiber.child.sibling 来更新 subtreeFlagschildLanes,以标记子树的更新状态。这样就可以通过 fiber.subtreeFlags 的值来快速判断子树是否包含副作用钩子,避免了深度遍历整个子树的开销

mount 阶段

createInstance

createInstance 函数的作用是为 Fiber 节点创建对应的 DOM 节点

源码地址 createInstance | react-dom/src/client/ReactDOMHostConfig.js

ts
function createInstance(
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
  internalInstanceHandle: Object,
): Instance {
  let parentNamespace: string
  if (__DEV__) {
    // ... 省略 DEV 环境下的代码
  } else {
    parentNamespace = hostContext
  }
  // 创建 DOM 节点
  const domElement: Instance = createElement(type, props, rootContainerInstance, parentNamespace)
  // 将当前 Fiber 节点挂载到 DOM 节点上
  precacheFiberNode(internalInstanceHandle, domElement)
  // 将当前的 props 挂载到 DOM 节点
  updateFiberProps(domElement, props)
  return domElement
}

appendAllChildren

appendAllChildren 函数会遍历传入的 workInProgress 的子节点,并将这些子节点的 stateNode 插入到父节点中

源码地址 appendAllChildren | react-reconciler/src/ReactFiberCompleteWork.old.js

ts
appendAllChildren = function (
  parent: Instance,
  workInProgress: Fiber,
  needsVisibilityToggle: boolean,
  isHidden: boolean,
) {
  // 遍历 workInProgress 的子节点
  let node = workInProgress.child
  while (node !== null) {
    if (node.tag === HostComponent || node.tag === HostText) {
      // 如果当前节点是 DOM 节点或文本节点,则将其直接插入到父节点中
      appendInitialChild(parent, node.stateNode)
    } else if (node.tag === HostPortal) {
      // If we have a portal child, then we don't want to traverse
      // down its children. Instead, we'll get insertions from each child in
      // the portal directly.
      // 如果当前节点是 HostPortal 则不需要遍历其子节点
    } else if (node.child !== null) {
      // 如果当前节点不是 DOM 节点或文本节点且存在子节点
      // 则将子节点的 return 属性指向当前节点,然后继续遍历处理子节点
      node.child.return = node
      node = node.child
      continue
    }

    // 当遍历结果为 workInProgress 时说明当前节点的子节点已经遍历完毕
    if (node === workInProgress) {
      return
    }

    // 遍历找到当前节点的下一个兄弟节点
    while (node.sibling === null) {
      // 当前节点没有父节点或父节点是 workInProgress 时遍历结束
      if (node.return === null || node.return === workInProgress) {
        return
      }
      // 向上迭代至父节点,寻找有兄弟节点的节点
      node = node.return
    }
    // 将兄弟节点的 return 指向同一个父节点
    node.sibling.return = node.return
    // 继续遍历处理下一个兄弟节点
    node = node.sibling
  }
}

finalizeInitialChildren

finalizeInitialChildren 函数会调用 setInitialProperties 来进行属性和事件的设置,然后根据 DOM 节点的类型来判断是否需要聚焦

源码地址 finalizeInitialChildren | react-dom/src/client/ReactDOMHostConfig.js

ts
function finalizeInitialChildren(
  domElement: Instance,
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
): boolean {
  // 属性和事件的设置
  setInitialProperties(domElement, type, props, rootContainerInstance)

  // 是否需要聚焦
  switch (type) {
    case 'button':
    case 'input':
    case 'select':
    case 'textarea':
      return !!props.autoFocus
    case 'img':
      return true
    default:
      return false
  }
}

setInitialProperties

setInitialProperties 函数用于设置 DOM 节点的属性以及事件监听

源码地址 setInitialProperties | react-dom/src/client/ReactDOMComponent.js

ts
function setInitialProperties(
  domElement: Element,
  tag: string,
  rawProps: Object,
  rootContainerElement: Element | Document | DocumentFragment,
): void {
  // 判断是否为自定义组件
  const isCustomComponentTag = isCustomComponent(tag, rawProps)

  let props: Object
  // 根据 tag 来处理不同的事件和属性
  switch (
    tag
    // ... 省略大量代码
  ) {
  }

  // 校验 props 是否合法
  assertValidProps(tag, props)

  // 设置 DOM 节点的属性
  setInitialDOMProperties(tag, domElement, rootContainerElement, props, isCustomComponentTag)

  // 对特定标签进行后续处理
  switch (tag) {
    case 'input':
      track(domElement)
      ReactDOMInputPostMountWrapper(domElement, rawProps, false)
      break
    // ... 省略大量代码
    case 'textarea':
    case 'option':
    case 'select':
    default:
      if (typeof props.onClick === 'function') {
        trapClickOnNonInteractiveElement(domElement)
      }
      break
  }
}

update 阶段

  • updateHostContainer 处理根节点
  • updateHostComponent 处理普通 DOM 节点
  • updateHostText 处理文本节点

这里只分析 updateHostComponent

updateHostComponent

源码地址 updateHostComponent | react-reconciler/src/ReactFiberCompleteWork.old.js

ts
updateHostComponent = function (
  current: Fiber,
  workInProgress: Fiber,
  type: Type,
  newProps: Props,
  rootContainerInstance: Container,
) {
  // If we have an alternate, that means this is an update and we need to
  // schedule a side-effect to do the updates.
  const oldProps = current.memoizedProps
  if (oldProps === newProps) {
    // In mutation mode, this is sufficient for a bailout because
    // we won't touch this node even if children changed.
    return
  }

  // If we get updated because one of our children updated, we don't
  // have newProps so we'll have to reuse them.
  // TODO: Split the update API as separate for the props vs. children.
  // Even better would be if children weren't special cased at all tho.
  const instance: Instance = workInProgress.stateNode
  const currentHostContext = getHostContext()

  // 对比新旧 props 的差异,生成更新 payload
  const updatePayload = prepareUpdate(
    instance,
    type,
    oldProps,
    newProps,
    rootContainerInstance,
    currentHostContext,
  )
  workInProgress.updateQueue = updatePayload

  // 如果 payload 存在,标记当前节点需要更新(所有的更新操作在 commitWork 阶段执行)
  if (updatePayload) {
    markUpdate(workInProgress)
  }
}

updatePayload 的值是一个数组

  • 偶数索引的值为变化的 prop key
  • 奇数索引的值为变化的 prop value

以下面的代码举 🌰

tsx
function UpdatePayload() {
  const [state, setState] = useState(0)

  return (
    <button state={state} name={`maomao ${state * 2}`} onClick={() => setState((v) => v + 1)}>
      点击 +1
    </button>
  )
}

updatePayload

如有转载或 CV 的请标注本站原文地址