Skip to content

beginWork(FiberNode 更新入口)

这个在 React 的源码中是一个单独的文件 react-reconciler/src/ReactFiberBeginWork.new.js

这个阶段的主要特点是:

  1. 产生 current (没有 current 的说明是初次渲染,然后在 beginWork 的流程通过 workInProgree 生成 FiberNode,并存在 alternate 上,作为下次的 current )
  2. 根据 lanes 和 childLanes 生成 FiberNode

其主要作用是:根据组件类型的不同生成对应的 FiberNode 对象,并在 update 的过程判断原 FiberNode 是否可以复用

主要流程

  • 判断节点是否可以复用 (didReceiveUpdate)

  • 根据不同的节点类型去调用对应的 渲染方法

    • IndeterminateComponent => mountIndeterminateComponent
    • LazyComponent => mountLazyComponent
    • FunctionComponent => updateFunctionComponent
    • ClassComponent => updateClassComponent
    • HostRoot => updateHostRoot
    • HostComponent => updateHostComponent
    • HostText => updateHostText
    • SuspenseComponent => updateSuspenseComponent
    • HostPortal => updatePortalComponent
    • ForwardRef => updateForwardRef
    • Fragment => updateFragment
    • ContextProvider => updateContextProvider
    • MemoComponent => updateMemoComponent
    • SimpleMemoComponent => updateSimpleMemoComponent
  • FunctionComponent 类型的

    • renderWithHooks
    • reconcileChildren
    • mountChildFibers | reconcileChildFibers

FiberNode 复用和强制更新

我们看一下判断条件

js
// 如果 current 存在,那么说明这个FiberNode节点是更新流程
if (current !== null) {
  // 获取老的 props
  const oldProps = current.memoizedProps;
  // 获取新的 props
  const newProps = workInProgress.pendingProps;

  // 如果
  // 1. 新老 props 不相等, 这边是对象的地址比较
  // 2. context 改变了
  // 3. 新旧组件的类型不同
  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).
    didReceiveUpdate = true;
  } else {
    // Neither props nor legacy context changes. Check if there's a pending
    // update or context change.
    const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
      current,
      renderLanes
    );
    // 2. 如果当前FiberNode节点不存在待更新的任务,也不存在上下文更新,那么就可以强制不更新了
    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.
      // 3. 如果存在 DidCapture 标记,那么说明这个节点存在 错误边界 或者  suspense 边界,那么也不能复用
      (workInProgress.flags & DidCapture) === NoFlags
    ) {
      // No pending updates or context. Bail out now.
      // 不需要更新
      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.
      didReceiveUpdate = true;
    } else {
      didReceiveUpdate = false;
    }
  }
} else {
  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);
  }
}

从上面的代码我们可以看到对于组件的复用的条件有以下几种

    1. 如果 新老 props 不相等 或者 context 改变了 或者 新旧组件的类型不同,那么就可以复用了
    1. 不存在待更新的任务,也不存在上下文更新,那么就可以强制不更新了
    1. 存在 DidCapture 标记,那么说明这个节点存在 错误边界 或者 suspense 边界,那么也不能复用
    1. 存在 ForceUpdateForLegacySuspense 标记,那么说明这个节点存在 错误边界 或者 suspense 边界,那么也不能复用
问题
  1. 在第一次判断 hasLegacyContextChanged() 是否改变后,又判断了 hasScheduledUpdateOrContext 是否改变,这是为什么?

根据节点的类型去生成对应的 FiberNode

参考

beginWork