React源码解析(四):beginWork与completeWork源码解析

487 阅读4分钟

beginWork

对整棵树的每一个节点进行更新操作,通过 switch (workInProgress.tag) 对不同的组件做不同的更新处理,更新当前节点 workInProgress,获取新的 children,并为新的 children 生成他们对应的 Fiber,并最终返回第一个子节点 child

  • beginWork 首先会判断 current 是否为 null,当首次渲染时,current 必然指向 null;
  • 如果 current 不为 null,则分别从 currentworkInProgress 拿到新旧 props,当新旧 props 不一致,或者 context 发生改变,则将 didReceiveUpdate 设为 true;
  • 新旧 props 和 legacyContext 均未改变,则调用 checkScheduledUpdateOrContext 检查是否有更新或者 new context(有别于 legacy context)改变。
if (
    oldProps !== newProps ||
    hasLegacyContextChanged()
) {
    didReceiveUpdate = true;
} else {
    // 此时 props 和 legacy context 均无变化,检查有没有
    // 检查 renderLanes 中是否包含 updateLanes,或者 current.dependencies 不为null
    // 且内部的 new context 链表有更新,则返回true
    const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes,
    );
    ...
}

如果 hasScheduledUpdateOrContext 为 false,则走优化路径,执行 attemptEarlyBailoutIfNoScheduledUpdate 函数,此函数会根据 workInProgress.tag 做一些特殊处理,并最终执行 bailoutOnAlreadyFinishedWork 返回 workInProgress.child 或者 null;

if (
    !hasScheduledUpdateOrContext &&
    // 如果捕获到错误或者暂时挂起
    (workInProgress.flags & DidCapture) === NoFlags
) {
    // No pending updates or context. Bail out now.
    didReceiveUpdate = false;
    return attemptEarlyBailoutIfNoScheduledUpdate(
        current,
        workInProgress,
        renderLanes,
    );
}
// 对特殊fiber节点做一些beginWork阶段必要的处理
function attemptEarlyBailoutIfNoScheduledUpdate(
  current: Fiber,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  switch (workInProgress.tag) {
    case HostRoot: ...
    case HostComponent: ...
    case ClassComponent: ...
    case HostPortal: ...
    case ContextProvider: ...
    case Profiler: ...
    case SuspenseComponent: ...
    case SuspenseListComponent: ...
    case OffscreenComponent: ...
    case LegacyHiddenComponent: ...
    case CacheComponent: ...
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}

总之,beginWork 第一阶段主要是在配置 didReceiveUpdate 全局属性,didReceiveUpdate 为 false 的情况有:

  • 新旧 props 不一致,新旧 props 的比较是全等比较,除非上一轮复用 workInProgress.child,否则; didReceiveUpdate 为 false 的情况有:
  • hasScheduledUpdateOrContext 为 false,没有更新(renderLanes 中不包含 workInProgress.lanes)且 context 没有更新;
  • current 为 null。

beginWork 第二阶段是根据 workInProgress.tag,执行不同的 update 函数生成 workInProgress 的所有子节点,并返回第一个子节点 workInProgress.child,其中不同的 update 函数都会调用 reconcileChildren 方法,这是 React diff 的核心过程,对比和创建新 Fiber 都在这里,我们下一章会详细讲解此方法源码。

completeWork

completeWork 的主要工作是根据 workInProgress.tag 执行不同的逻辑;主要工作是将子节点的一些属性冒泡到父节点,并把子节点的 return 属性指向父节点;这些处理由 bubbleProperties 完成;

bubbleProperties

bubbleProperties 首先会判断 beginWork 阶段生成的 workInProgress.child 是否经过优化得到,即通过 bailoutOnAlreadyFinishedWork 生成。

const didBailout =
       // workInProgress.alternate就是current 
    workInProgress.alternate !== null &&
    workInProgress.alternate.child === workInProgress.child;

遍历 workInProgress 所有的 child,将 child.laneschild.childLaneschild 所有子孙节点的 lanes)合并到 workInProgress.childLanes 中;将 child.subtreeFlagschild.flags 合并到 workInProgress.subtreeFlags;将 child.return 指向 workInProgress

while (child !== null) {
    newChildLanes = mergeLanes(
        newChildLanes,
        mergeLanes(child.lanes, child.childLanes),
    );
    // didBailout即前面所提到的 child 是否经优化得到
    // 如果不是
    if (!didBailout) {
        subtreeFlags |= child.subtreeFlags;
        subtreeFlags |= child.flags;
    // 如果是
    } else {
        subtreeFlags |= child.subtreeFlags & StaticMask;
        subtreeFlags |= child.flags & StaticMask;
    }
    child.return = workInProgress;
    child = child.sibling;
}
workInProgress.subtreeFlags |= subtreeFlags;
workInProgress.childLanes = newChildLanes;

总之,bubbleProperties 顾名思义,就是将子节点的属性往父节点叠加,即属性的冒泡;

switch (workInProgress.tag)

switch (workInProgress.tag) {
    case IndeterminateComponent:
    case LazyComponent:
    case SimpleMemoComponent:
    case FunctionComponent:
    case ForwardRef:
    case Fragment:
    case Mode:
    case Profiler:
    case ContextConsumer:
    case MemoComponent:
      // 直接执行 bubbleProperties 冒泡相关属性
      bubbleProperties(workInProgress);
      return null;
    case ClassComponent: {
      const Component = workInProgress.type;
      // 判断组件是否为 ContextProvider
      if (isLegacyContextProvider(Component)) {
        popLegacyContext(workInProgress);
      }
      // 执行 bubbleProperties 冒泡相关属性
      bubbleProperties(workInProgress);
      return null;
    }
    case HostRoot: {
        const fiberRoot = (workInProgress.stateNode: FiberRoot);
          if (enableCache) {
            popRootCachePool(fiberRoot, renderLanes);
            let previousCache: Cache | null = null;
            if (workInProgress.alternate !== null) {
                  previousCache = workInProgress.alternate.memoizedState.cache;
            }
            const cache: Cache = workInProgress.memoizedState.cache;
            if (cache !== previousCache) {
                  // Run passive effects to retain/release the cache.
                  workInProgress.flags |= Passive;
            }
            popCacheProvider(workInProgress, cache);
          }
        // 从ReactFiberStack中移除相关 contextStack contextFiber rootInstance
          popHostContainer(workInProgress);
        // 从ReactFiberStack中移除 LegacyContext
          popTopLevelLegacyContextObject(workInProgress);
          resetMutableSourceWorkInProgressVersions();
           // 配置顶层context对象,只有主动调用`renderSubtreeIntoContainer`时才会有用
          if (fiberRoot.pendingContext) {
            fiberRoot.context = fiberRoot.pendingContext;
            fiberRoot.pendingContext = null;
          }
          if (current === null || current.child === null) {
            const wasHydrated = popHydrationState(workInProgress);
            if (wasHydrated) {
                  workInProgress.flags |= Update;
            } else if (!fiberRoot.isDehydrated) {
                  workInProgress.flags |= Snapshot;
            }
          }
        // 下文介绍 updateHostContainer
          updateHostContainer(current, workInProgress);
          bubbleProperties(workInProgress);
          return null;
    }
    ...

updateHostContainer

updateHostContainer 函数

  • 先执行 hadNoMutationsEffects 判断子节点是否有变化;
const didBailout = current !== null && current.child === workInProgress.child;
// 如果 workInProgress.child 是经复用得到的,那肯定没有变化
if (didBailout) return true;
// 如果有删除子节点的标记,则肯定算变化
if ((workInProgress.flags & ChildDeletion) !== NoFlags) return false;

// 这里有一个React的TODO,应该有将 hadNoMutationsEffects 判断逻辑移到 
// 上一层逻辑 bubbleProperties 后面的打算
// TODO: If we move the `hadNoMutationsEffects` call after `bubbleProperties`
// then we only have to check the `completedWork.subtreeFlags`.
let child = workInProgress.child;
// 遍历所有一级子节点,只要有子节点 flags 中包含 MutationMask 就返回true,即有变化
// 而 child.flags 和 child.subtreeFlags 在 bubbleProperties 中会被并入父节点的
// 同名属性上
while (child !== null) {
    if (
        (child.flags & MutationMask) !== NoFlags ||
        (child.subtreeFlags & MutationMask) !== NoFlags
    ) {
        return false;
    }
    child = child.sibling;
}
// 默认无变化
return true;
  • 子节点有变化,则拿到 fiberRootcontainerInfo 属性,即 root 节点,render 方法接收的第二个参数;
  • 执行 appendAllChildrenToContainer 函数,将所有子节点加入到一个数组中;将 fiberRoot.pendingChildren 属性指向此数组;
  • 执行 finalizeContainerChildrencontainer.pendingChildren 属性指向新的子节点数组。