Loading...
墨滴

早睡wqi

2021/10/16  阅读:24  主题:默认主题

React render阶段解析三-completeWork入口流程

这次是接着上一期render阶段的文章,解析挂载时render阶段的"归"阶段----completeWork函数

completeWork开始阶段

在performUnitOfWork中执行完beginWork,就会进入下面判断 workInProgress.child

  if (next === null) {
    // workInProgress已经不存在子树,就开始进行"归"阶段
    completeUnitOfWork(unitOfWork);
  } else {
    // next是beginWork调用后的返回值workInProgress.child
    workInProgress = next;
  }

completeUnitOfWork

主要做了两件事,执行completeWork 和收拢EffectList

function completeUnitOfWork(unitOfWork: Fiber): void {
  //尝试完成当前的工作单元,然后移动到下一个兄弟。 如果没有更多的兄弟,返回到父fiber。
  let completedWork = unitOfWork;
  // 直到父节点为null,表示整棵 workInProgress fiber 树已处理完毕。
  do {
    // 记录父节点和当前节点的current树
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;

    // 检查工作是否完成或是否有东西抛出.
    if ((completedWork.effectTag & Incomplete) === NoEffect) {
      let next;
      if (
        !enableProfilerTimer ||
        (completedWork.mode & ProfileMode) === NoMode
      ) {
        // 执行completeWork,并把返回值赋值给next
        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;
      }

      resetChildLanes(completedWork);

      if (
        returnFiber !== null &&
        (returnFiber.effectTag & Incomplete) === NoEffect
      ) {
        // 执行effect相关
        if (returnFiber.firstEffect === null) {
          returnFiber.firstEffect = completedWork.firstEffect;
        }
        if (completedWork.lastEffect !== null) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
          }
          returnFiber.lastEffect = completedWork.lastEffect;
        }

        if (effectTag > PerformedWork) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = completedWork;
          } else {
            returnFiber.firstEffect = completedWork;
          }
          returnFiber.lastEffect = completedWork;
        }
      }
    } else {
      const next = unwindWork(completedWork, subtreeRenderLanes);

      // Because this fiber did not complete, don't reset its expiration time.

      if (next !== null) {
        next.effectTag &= HostEffectMask;
        workInProgress = next;
        return;
      }

      if (
        enableProfilerTimer &&
        (completedWork.mode & ProfileMode) !== NoMode
      ) {
       
        stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);

        let actualDuration = completedWork.actualDuration;
        let child = completedWork.child;
        while (child !== null) {
          actualDuration += child.actualDuration;
          child = child.sibling;
        }
        completedWork.actualDuration = actualDuration;
      }

      if (returnFiber !== null) {
        // Mark the parent fiber as incomplete and clear its effect list.
        returnFiber.firstEffect = returnFiber.lastEffect = null;
        returnFiber.effectTag |= Incomplete;
      }
    }

    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      // If there is more work to do in this returnFiber, do that next.
      workInProgress = siblingFiber;
      return;
    }
    // 赋值父节点
    completedWork = returnFiber;
    workInProgress = completedWork;
  } while (completedWork !== null);

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

运行流程

运行流程
运行流程

completeWork

如果说“递”阶段的 beginWork 方法主要是创建子节点,那么“归”阶段的 completeWork 方法则主要是创建当前节点的 DOM 节点,并对子节点的 DOM 节点和 EffectList 进行收拢。[1]很多类型是不进行处理,return null

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: {
      // ...省略
      return null;
    }
    case HostComponent: {
      // ...
      const type = workInProgress.type;
      if (current !== null && workInProgress.stateNode != null) {
      //更新dom节点
        updateHostComponent(
          current,
          workInProgress,
          type,
          newProps,
          rootContainerInstance,
        );

        if (enableDeprecatedFlareAPI) {
          const prevListeners = current.memoizedProps.DEPRECATED_flareListeners;
          const nextListeners = newProps.DEPRECATED_flareListeners;
          if (prevListeners !== nextListeners) {
            markUpdate(workInProgress);
          }
        }

        if (current.ref !== workInProgress.ref) {
          markRef(workInProgress);
        }
      } else {
        //...
        const currentHostContext = getHostContext();
        const wasHydrated = popHydrationState(workInProgress);
        if (wasHydrated) {
          // 服务端渲染相关
        } else {
          // 创建新的dom节点
          const instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );
          // 把fiber子节点的dom挂载到当前dom后面
          appendAllChildren(instance, workInProgress, falsefalse);

          workInProgress.stateNode = instance;

          if (enableDeprecatedFlareAPI) {
            const listeners = newProps.DEPRECATED_flareListeners;
            if (listeners != null) {
              updateDeprecatedEventListeners(
                listeners,
                workInProgress,
                rootContainerInstance,
              );
            }
          }

          if (
            // 初始化dom属性和事件
            finalizeInitialChildren(
              instance,
              type,
              newProps,
              rootContainerInstance,
              currentHostContext,
            )
          ) {
            markUpdate(workInProgress);
          }
        }

        if (workInProgress.ref !== null) {
          markRef(workInProgress);
        }
      }
      return null;
    }
  // ...省略
}
流程图
流程图

createInstance

新建节点,调用createElement创建dom节点

function createInstance(
  typestring,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
  internalInstanceHandle: Object,
): Instance 
{
  let parentNamespace: string;
  if (__DEV__) {
    // TODO: take namespace into account when validating.
    const hostContextDev = ((hostContext: any): HostContextDev);
    validateDOMNesting(typenull, hostContextDev.ancestorInfo);
    if (
      typeof props.children === 'string' ||
      typeof props.children === 'number'
    ) {
      const string = '' + props.children;
      const ownAncestorInfo = updatedAncestorInfo(
        hostContextDev.ancestorInfo,
        type,
      );
      validateDOMNesting(nullstring, ownAncestorInfo);
    }
    parentNamespace = hostContextDev.namespace;
  } else {
    parentNamespace = ((hostContext: any): HostContextProd);
  }
  const domElement: Instance = createElement(
    type,
    props,
    rootContainerInstance,
    parentNamespace,
  );
  precacheFiberNode(internalInstanceHandle, domElement);
  // 更新属性
  updateFiberProps(domElement, props);
  return domElement;
}

appendAllChildren

把fiber子节点的dom挂载到当前dom后面

 appendAllChildren = function(
    parent: Instance,
    workInProgress: Fiber,
    needsVisibilityToggle: boolean,
    isHidden: boolean,
  
{
    let node = workInProgress.child;
    while (node !== null) {
      if (node.tag === HostComponent || node.tag === HostText) {
        // stateNode挂载节点的dom
        appendInitialChild(parent, node.stateNode);
      } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
        appendInitialChild(parent, node.stateNode.instance);
      } else if (node.tag === HostPortal) {
      } else if (node.child !== null) {
        // 针对一些特殊类型的子节点,如<Fragment />,尝试从子节点的子节点获取DOM
        // 存在子节点就继续遍历子节点
        node.child.return = node;
        node = node.child;
        continue;
      }
      if (node === workInProgress) {
        return;
      }
      while (node.sibling === null) {
        if (node.return === null || node.return === workInProgress) {
          return;
        }
        node = node.return;
      }
      node.sibling.return = node.return;
      // 将node.sibling作为下次循环的主体
      node = node.sibling;
    }
  };

// 执行了原生的appendChild方法
export function appendInitialChild(parentInstance: Instance, child: Instance | TextInstance): void {
  parentInstance.appendChild(child);
}

updateHostComponent

更新旧的dom节点,主要作用就是计算出需要变化的 DOM 节点属性,并给当前节点打上Update的EffectTag。

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;
    // props没有变化就直接返回
    if (oldProps === newProps) {
      return;
    }

    
    const updatePayload = prepareUpdate(
      instance,
      type,
      oldProps,
      newProps,
      rootContainerInstance,
      currentHostContext,
    );
    // 将计算出来的updatePayload挂载在workInProgress.updateQueue上,供后续commit阶段使用
    workInProgress.updateQueue = (updatePayload: any);
    // 如果updatePayload不为空,则给当前节点打上Update的EffectTag
    if (updatePayload) {
      markUpdate(workInProgress);
    }
  };

总结

  1. completeUnitOfWork方法主要循环执行completeWork,父元素为空或者存在兄弟节点就会进行下一轮render阶段解析,生成兄弟节点的fiber。
  2. completeWork主要是生成当前fiber的dom节点,并且挂载连接子节点的dom
  3. completeWork主要使用createInstance新建节点和updateHostComponent更新节点操作。
  4. 最终结束completeUnitOfWork执行,进入commit阶段(下个文章开始讲,敬请期待)
流程
流程

参考资料

[1]

React 源码解析系列 - React 的 render 阶段(三):completeUnitOfWork: https://segmentfault.com/a/1190000040742732?utm_source=sf-similar-article。

早睡wqi

2021/10/16  阅读:24  主题:默认主题

作者介绍

早睡wqi