
beginWork “递”阶段 
beginWork 主要工作是根据传入的 Fiber 节点 创建或复用 子 Fiber 节点
beginWork流程图
beginWork 的入参 
源码地址 beginWork | react-reconciler/src/ReactFiberBeginWork.old.js
ts
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  // 优先级相关(先不管这个参数)
  renderLanes: Lanes,
): Fiber | null {
  // ... 省略函数体
}在之前解读 performUnitOfWork 时说过 current 和 workInProgress 这两个重要变量,这里再重复一下:
- current表示当前页面正在使用的- Fiber 节点,即- workInProgress.alternate
- workInProgress表示当前正在构建的- Fiber 节点
beginWork 工作的两部分 
在 Fiber 双缓存的构建 时说过,除 rootFiber 以外组件 mount 时 current === null,而组件 update 时,由于已经 mount 过了,所以 current !== null(即 current === null ? mount : update)
根据这个特性可以将 beginWork 的工作分为两部分:
- update时:如果- current存在且满足一定的条件时,可以复用- current的- Fiber 节点(即上一次更新的- Fiber 节点)
- mount时:根据- fiber.tag进入不同的处理函数,然后调用- reconcileChildren创建- 子 Fiber 节点
ts
function beginWork(current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes): Fiber | null {
  /* update 时,如果 current 存在可能存在优化路径,可以复用上一次更新的 Fiber 节点 */
  if (current !== null) {
    const oldProps = current.memoizedProps
    const newProps = workInProgress.pendingProps
    if (
      oldProps !== newProps ||
      hasLegacyContextChanged() ||
      // Force a re-render if the implementation changed due to hot reload:
      (__DEV__ ? workInProgress.type !== current.type : false)
    ) {
      // If props or context changed, mark the fiber as having performed work.
      // This may be unset if the props are determined to be equal later (memo).
      // 当 props 或 context 发生变化时将 Fiber 标记为需要更新
      didReceiveUpdate = true
    } else {
      // checkScheduledUpdateOrContext 函数用于检查是否有挂起的更新或 context 变化
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes)
      if (
        !hasScheduledUpdateOrContext &&
        // If this is the second pass of an error or suspense boundary, there
        // may not be work scheduled on `current`, so we check for this flag.
        (workInProgress.flags & DidCapture) === NoFlags
      ) {
        // 当没有挂起的更新或 context 时,复用 current 的节点
        didReceiveUpdate = false
        return attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes)
      }
      if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
        // This is a special case that only exists for legacy mode.
        // See https://github.com/facebook/react/pull/19216.
        // legacy 模式强制更新
        didReceiveUpdate = true
      } else {
        // An update was scheduled on this fiber, but there are no new props
        // nor legacy context. Set this to false. If an update queue or context
        // consumer produces a changed value, it will set this to true. Otherwise,
        // the component will assume the children have not changed and bail out.
        // 在这个 Fiber 节点上安排了一个更新,但由于 props 和 context 都没有发生变化,将其设置为 false
        // 如果更新队列或 context 消费者生成了更改的值,则将其设置为 true。否则该组件会设置 children 没有更新并退出
        didReceiveUpdate = false
      }
    }
  } else {
    /* mount 时,根据 tag 创建不同类型的子 Fiber 节点 */
    didReceiveUpdate = false
    if (getIsHydrating() && isForkedChild(workInProgress)) {
      // Check if this child belongs to a list of muliple children in
      // its parent.
      //
      // In a true multi-threaded implementation, we would render children on
      // parallel threads. This would represent the beginning of a new render
      // thread for this subtree.
      //
      // We only use this for id generation during hydration, which is why the
      // logic is located in this special branch.
      const slotIndex = workInProgress.index
      const numberOfForks = getForksAtLevel(workInProgress)
      pushTreeId(workInProgress, numberOfForks, slotIndex)
    }
  }
  // Before entering the begin phase, clear pending update priority.
  // TODO: This assumes that we're about to evaluate the component and process
  // the update queue. However, there's an exception: SimpleMemoComponent
  // sometimes bails out later in the begin phase. This indicates that we should
  // move this assignment out of the common path and into each branch.
  workInProgress.lanes = NoLanes
  // 根据 tag 进入不同的处理函数,然后调用 reconcileChildren 创建 Fiber 节点
  switch (workInProgress.tag) {
    // ... 省略其他 case
    // 函数组件
    case FunctionComponent: {
      const Component = workInProgress.type
      const unresolvedProps = workInProgress.pendingProps
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps)
      return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderLanes)
    }
    // 类组件
    case ClassComponent: {
      const Component = workInProgress.type
      const unresolvedProps = workInProgress.pendingProps
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps)
      return updateClassComponent(current, workInProgress, Component, resolvedProps, renderLanes)
    }
    // rootFiber
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes)
    // 普通 DOM 标签
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes)
    // 文本节点
    case HostText:
      return updateHostText(current, workInProgress)
    // ... 省略其他 case
  }
  // 当 switch 语句中没有匹配到任何类型时,抛出错误
  throw new Error(
    `Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
      'React. Please file an issue.'
  )
}React 中的节点类型定义如下:
ts
export const FunctionComponent = 0
export const ClassComponent = 1
export const IndeterminateComponent = 2 // Before we know whether it is function or class
export const HostRoot = 3 // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4 // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5
export const HostText = 6
export const Fragment = 7
export const Mode = 8
export const ContextConsumer = 9
export const ContextProvider = 10
export const ForwardRef = 11
export const Profiler = 12
export const SuspenseComponent = 13
export const MemoComponent = 14
export const SimpleMemoComponent = 15
export const LazyComponent = 16
export const IncompleteClassComponent = 17
export const DehydratedFragment = 18
export const SuspenseListComponent = 19
export const ScopeComponent = 21
export const OffscreenComponent = 22
export const LegacyHiddenComponent = 23
export const CacheComponent = 24
export const TracingMarkerComponent = 25updateHostComponent 
updateHostComponent 函数用于处理普通 DOM 标签
源码地址 updateHostComponent | react-reconciler/src/ReactFiberBeginWork.old.js
ts
function updateHostComponent(current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes) {
  pushHostContext(workInProgress)
  if (current === null) {
    tryToClaimNextHydratableInstance(workInProgress)
  }
  const type = workInProgress.type
  const nextProps = workInProgress.pendingProps
  const prevProps = current !== null ? current.memoizedProps : null
  let nextChildren = nextProps.children
  // 判断是否是纯文本或是否具有 dangerouslySetInnerHTML 属性(即内容不需要 React 来渲染)
  const isDirectTextChild = shouldSetTextContent(type, nextProps)
  // 如果是纯文本或具有 dangerouslySetInnerHTML 属性,那么将 nextChildren 设置为 null
  if (isDirectTextChild) {
    nextChildren = null
  } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
    // 如果从纯文本节点切换到普通节点或空节点时,需要打上 ContentReset 标记
    workInProgress.flags |= ContentReset
  }
  markRef(current, workInProgress)
  // 调用 reconcileChildren 创建子 Fiber 节点
  reconcileChildren(current, workInProgress, nextChildren, renderLanes)
  return workInProgress.child
}shouldSetTextContent 
源码地址 shouldSetTextContent | react-dom/src/client/ReactDOMHostConfig.js
ts
function shouldSetTextContent(type: string, props: Props): boolean {
  return (
    type === 'textarea' ||
    type === 'noscript' ||
    typeof props.children === 'string' ||
    typeof props.children === 'number' ||
    (typeof props.dangerouslySetInnerHTML === 'object' &&
      props.dangerouslySetInnerHTML !== null &&
      props.dangerouslySetInnerHTML.__html != null)
  )
}attemptEarlyBailoutIfNoScheduledUpdate 
源码地址 attemptEarlyBailoutIfNoScheduledUpdate | react-reconciler/src/ReactFiberBeginWork.old.js
ts
function attemptEarlyBailoutIfNoScheduledUpdate(
  current: Fiber,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  // 根据 tag 进入不同的处理函数
  switch (workInProgress.tag) {
    case HostRoot:
      pushHostRootContext(workInProgress)
      const root: FiberRoot = workInProgress.stateNode
      pushRootTransition(workInProgress, root, renderLanes)
      if (enableCache) {
        const cache: Cache = current.memoizedState.cache
        pushCacheProvider(workInProgress, cache)
      }
      resetHydrationState()
      break
    case HostComponent:
      pushHostContext(workInProgress)
      break
    case ClassComponent: {
      const Component = workInProgress.type
      if (isLegacyContextProvider(Component)) {
        pushLegacyContextProvider(workInProgress)
      }
      break
    }
    // ... 省略其他 case
  }
  // 调用 bailoutOnAlreadyFinishedWork 函数复用 Fiber 节点
  return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
}bailoutOnAlreadyFinishedWork 
源码地址 bailoutOnAlreadyFinishedWork | react-reconciler/src/ReactFiberBeginWork.old.js
ts
function bailoutOnAlreadyFinishedWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  if (current !== null) {
    // 复用之前的依赖
    workInProgress.dependencies = current.dependencies
  }
  if (enableProfilerTimer) {
    // Don't update "base" render times for bailouts.
    stopProfilerTimerIfRunning(workInProgress)
  }
  markSkippedUpdateLanes(workInProgress.lanes)
  // 检查子节点是否有任何待处理的工作
  if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
    if (enableLazyContextPropagation && current !== null) {
      lazilyPropagateParentContextChanges(current, workInProgress, renderLanes)
      if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
        return null
      }
    } else {
      return null
    }
  }
  // 克隆子 Fiber
  cloneChildFibers(current, workInProgress)
  return workInProgress.child
}reconcileChildren 
reconcileChildren 函数用于创建或更新 Fiber 树,会根据 current === null 的结果执行不同的逻辑
源码地址 reconcileChildren | react-reconciler/src/ReactFiberBeginWork.old.js
ts
export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes,
) {
  if (current === null) {
    // If this is a fresh new component that hasn't been rendered yet, we
    // won't update its child set by applying minimal side-effects. Instead,
    // we will add them all to the child before it gets rendered. That means
    // we can optimize this reconciliation pass by not tracking side-effects.
    // mount 阶段,这是一个还未渲染过的全新组件,可以不用考虑对比最小副作用来更新它的子节点
    // 可以在它渲染前添加上所有的子节点,意味着我们可以不对比副作用来优化这个操作
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes)
  } else {
    // If the current child is the same as the work in progress, it means that
    // we haven't yet started any work on these children. Therefore, we use
    // the clone algorithm to create a copy of all the current children.
    // 如果 current 的子节点和 workInProgress 的子节点相同,意味着我们还没有开始对这些子节点进行任何操作
    // 因此,将使用克隆算法来创建当前子节点的副本
    // If we had any progressed work already, that is invalid at this point so
    // let's throw it out.
    // 如果 workInProgress 有 child,但是 current 没有 child,那么说明 workInProgress 的 child 是无效的,需要丢弃
    // update 时,current 为上一次渲染的 Fiber 节点
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes,
    )
  }
}ts
export const reconcileChildFibers = ChildReconciler(true)
export const mountChildFibers = ChildReconciler(false)TIP
reconcileChildFibers 和 mountChildFibers 最终都是 ChildReconciler 传递不同的参数返回的函数;在 ChildReconciler 函数中用 shouldTrackSideEffects 来判断是否需要跟踪副作用(即为对应的 Fiber 节点 添加 flags 属性),因此 reconcileChildFibers 和 mountChildFibers 的不同在于对副作用的处理不同
ChildReconciler 
ChildReconciler 中定义了大量如 deleteXXX、placeXXX、updateXXX、reconcileXXX 的函数,这些函数覆盖了对 Fiber 节点 的创建、增加、删除、修改等操作,而这些函数将直接或间接地被 reconcileChildFibers 所调用
源码地址 ChildReconciler | react-reconciler/src/ReactChildFiber.old.js
ts
function ChildReconciler(shouldTrackSideEffects) {
  /* 以下函数省略函数体 */
  function deleteChild(returnFiber, childToDelete) {}
  function deleteRemainingChildren(returnFiber, currentFirstChild) {}
  function mapRemainingChildren(returnFiber, currentFirstChild) {}
  function useFiber(fiber, pendingProps) {}
  function placeChild(newFiber, lastPlacedIndex, newIndex) {}
  function placeSingleChild(newFiber) {}
  function updateTextNode(returnFiber, current, textContent, lanes) {}
  function updateElement(returnFiber, current, element, lanes) {}
  function updatePortal(returnFiber, current, portal, lanes) {}
  function updateFragment(returnFiber, current, fragment, lanes, key) {}
  function createChild(returnFiber, newChild, lanes) {}
  function updateSlot(returnFiber, oldFiber, newChild, lanes) {}
  function updateFromMap(existingChildren, returnFiber, newIdx, newChild, lanes) {}
  function warnOnInvalidKey(child, knownKeys, returnFiber) {}
  function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren, lanes) {}
  function reconcileChildrenIterator(returnFiber, currentFirstChild, newChildrenIterable, lanes) {}
  function reconcileSingleTextNode(returnFiber, currentFirstChild, textContent, lanes) {}
  function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {}
  function reconcileSinglePortal(returnFiber, currentFirstChild, portal, lanes) {}
  // This API will tag the children with the side-effect of the reconciliation
  // itself. They will be added to the side-effect list as we pass through the
  // children and the parent.
  function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    lanes: Lanes,
  ): Fiber | null {
    debugger
    // This function is not recursive.
    // If the top level item is an array, we treat it as a set of children,
    // not as a fragment. Nested arrays on the other hand will be treated as
    // fragment nodes. Recursion happens at the normal flow.
    // Handle top level unkeyed fragments as if they were arrays.
    // This leads to an ambiguity between <>{[...]}</> and <>...</>.
    // We treat the ambiguous cases above the same.
    // 判断最外层的 newChild 是否是 Fragment 组件
    const isUnkeyedTopLevelFragment =
      typeof newChild === 'object' &&
      newChild !== null &&
      newChild.type === REACT_FRAGMENT_TYPE &&
      newChild.key === null
    // 如果最外层是 Fragment 组件,那么将其 children 作为 newChild
    if (isUnkeyedTopLevelFragment) {
      newChild = newChild.props.children
    }
    // 判断 newChild 是否是对象
    if (typeof newChild === 'object' && newChild !== null) {
      switch (newChild.$$typeof) {
        // 处理 jsx 节点
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(
            reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes),
          )
        // 处理 portal 节点
        case REACT_PORTAL_TYPE:
          return placeSingleChild(
            reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes),
          )
        // 处理 lazy 节点
        case REACT_LAZY_TYPE:
          const payload = newChild._payload
          const init = newChild._init
          // TODO: This function is supposed to be non-recursive.
          return reconcileChildFibers(returnFiber, currentFirstChild, init(payload), lanes)
      }
      // 处理多个子节点
      if (isArray(newChild)) {
        return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, lanes)
      }
      // 处理可迭代的子节点
      if (getIteratorFn(newChild)) {
        return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, lanes)
      }
      throwOnInvalidObjectType(returnFiber, newChild)
    }
    // 处理文本节点
    if ((typeof newChild === 'string' && newChild !== '') || typeof newChild === 'number') {
      return placeSingleChild(
        reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, lanes),
      )
    }
    // 删除剩余的子节点
    return deleteRemainingChildren(returnFiber, currentFirstChild)
  }
  return reconcileChildFibers
}flags 属性 
flags 属性用于标记 Fiber 节点 的状态,其值为二进制数,每一位代表一种状态,如下:
ts
export const Placement = /*                    */ 0b00000000000000000000000010
export const Update = /*                       */ 0b00000000000000000000000100
export const Deletion = /*                     */ 0b00000000000000000000001000
export const ChildDeletion = /*                */ 0b00000000000000000000010000
export const ContentReset = /*                 */ 0b00000000000000000000100000
export const Callback = /*                     */ 0b00000000000000000001000000
export const DidCapture = /*                   */ 0b00000000000000000010000000
export const ForceClientRender = /*            */ 0b00000000000000000100000000
export const Ref = /*                          */ 0b00000000000000001000000000
export const Snapshot = /*                     */ 0b00000000000000010000000000
export const Passive = /*                      */ 0b00000000000000100000000000
export const Hydrating = /*                    */ 0b00000000000001000000000000
export const Visibility = /*                   */ 0b00000000000010000000000000
export const StoreConsistency = /*             */ 0b00000000000100000000000000在通知 Renderer 将 Fiber 节点 对应的 DOM 节点 渲染到页面上需要满足以下两个条件:
- fiber.stateNode存在(即- Fiber 节点中保存了对应的- DOM 节点)
- fiber.flags中包含- Placement标记(即- DOM 节点需要被插入到页面上)
在首屏渲染时如何满足上述两个条件?
- fiber.stateNode会在- completeWork中创建
- 在 mount时只有rootFiber会赋值Placement flags,其他Fiber 节点都不会赋值Placement flags,因此在commit阶段,插入操作仅会在根节点执行一次,避免了重复的 DOM 操作
如果在 mountChildFibers 时也会赋值 Placement flags,那么整棵 Fiber 的每个节点都会具有 Placement flags,这会导致在 commit 段执行大量的 DOM 插入操作,效率极低;
因此 React 进行了优化:mount 时只有 rootFiber 会赋值 Placement flags,从而确保在 commit 阶段只有一次插入操作,有效地提升了渲染性能

