likes
comments
collection
share

React源码系列(七):render阶段completeWork流程解析

作者站长头像
站长
· 阅读数 36

前言

这是React源码系列专栏的第六篇文章,预计写10篇左右,之前的文章请查看文末,通过本专栏的学习,相信大家可以快速掌握React源码的相关概念以及核心思想,向成为大佬的道路上更近一步; 本章我们学习render阶段completeWork流程,本系列源码基于v18.2.0版本;

render阶段的主要工作是构建Fiber树和生成effectList。

我们接着上一章继续讲解completeWork流程。

调用completeWork

performUnitOfWork 每次会尝试调用 beginWork 来创建当前节点的子节点,若创建出的子节点为空,则说明当前节点是一个叶子节点。按照深度优先遍历的原则,当遍历到叶子节点时,递阶段就结束了也就是beginWork阶段完成。随之而来进入归阶段,也就是 completeUnitOfWork,执行当前节点对应的 completeWork 逻辑。

function performUnitOfWork(unitOfWork: Fiber): void {
  const current = unitOfWork.alternate;
  setCurrentDebugFiberInDEV(unitOfWork);

  let next;
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
  }

  resetCurrentDebugFiberInDEV();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner.current = null;
}

completeUnitOfWork中会调用completeWork的逻辑,completeUnitOfWork的逻辑在下面会讲。

function completeUnitOfWork(unitOfWork: Fiber): void {
  
   /** ...省略... **/
  
  next = completeWork(current, completedWork, subtreeRenderLanes);

   /** ...省略... **/
}

进入completeWork流程

我们对照着下面这张completeWork的图来梳理一下流程。 React源码系列(七):render阶段completeWork流程解析 completeWork主要工作是处理fiber的props、创建dom,根据 workInProgress 节点的 tag 属性的不同,进入不同的 DOM 节点的创建、处理逻辑,向上冒泡副作用操作。我们提取一下主要的逻辑(看注释部分即可)。

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:
      bubbleProperties(workInProgress); // 将flags冒泡到父节点
      return null;
    case ClassComponent: {
      const Component = workInProgress.type;
      if (isLegacyContextProvider(Component)) {
        popLegacyContext(workInProgress);
      }
      bubbleProperties(workInProgress);
      return null;
    }
      
    // 我们直接挑HostComponent这段逻辑讲解
    case HostComponent: {
      popHostContext(workInProgress);
      const rootContainerInstance = getRootHostContainer();
      const type = workInProgress.type;
      
       // 判断 current 节点是否存在,因为目前是挂载阶段,因此 current 节点是不存在的
      if (current !== null && workInProgress.stateNode != null) {
        
        // update
        updateHostComponent(
          current,
          workInProgress,
          type,
          newProps,
          rootContainerInstance
        );

        if (current.ref !== workInProgress.ref) {
          markRef(workInProgress);
        }
      } else {
        // mount
        
	     // 为 DOM 节点的创建做准备
        const currentHostContext = getHostContext();
        const wasHydrated = popHydrationState(workInProgress);
        
         // 判断是否是服务端渲染,这里不重点关注
        if (wasHydrated) {
          if (
            prepareToHydrateHostInstance(
              workInProgress,
              rootContainerInstance,
              currentHostContext
            )
          ) {
            markUpdate(workInProgress);
          }
        } else {
         // 这一步很关键, createInstance 的作用是创建 DOM 节点
          const instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress
          );
          
    	   // appendAllChildren 会尝试把上一步创建好的 DOM 节点挂载到 DOM 树上去
          appendAllChildren(instance, workInProgress, false, false);
          
          // stateNode 用于存储当前 Fiber 节点对应的 DOM 节点
          workInProgress.stateNode = instance;
          if (
            
            // finalizeInitialChildren 用来为 DOM 节点设置属性
            finalizeInitialChildren(
              instance,
              type,
              newProps,
              rootContainerInstance,
              currentHostContext
            )
          ) {
            markUpdate(workInProgress);
          }
        }

        if (workInProgress.ref !== null) {
          markRef(workInProgress);
        }
      }
      bubbleProperties(workInProgress);
      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更新subtreeFlags和childLanes, 主要是为了标记子树有没有更新, 这样可以通过 fiber.subtreeFlags 快速判断子树是否有副作用钩子,不需要深度遍历. 在React18版本使用subtreeFlags替换了finishWork.firstEffect的副作用链表, 操作主要发生在bubbleProperties函数中。 completeWork 这段代码逻辑包含以下几点:

  • update时,调用updateHostComponent处理props(包括onClick、style、children ...),并将处理好的props赋值给updatePayload,最后会保存在workInProgress.updateQueue上。
  • mount时,调用createInstance创建dom,将后代dom节点插入刚创建的dom中,调用finalizeInitialChildren处理props,简单总结为以下3点。
    • 创建DOM 节点(CreateInstance)
    • 将 DOM 节点插入到 DOM 树中(AppendAllChildren)
    • 为 DOM 节点设置属性(FinalizeInitialChildren)

completeUnitOfWork

completeUnitOfWork 的作用是开启一个循环,它会反复做下面几件事。

  • 针对传入的当前节点,调用 completeWork;
  • 向上冒泡副作用生成subtreeFlags,也就是completeWork中的bubbleProperties逻辑;
  • 以当前节点为起点,循环遍历其兄弟节点及其父节点。当遍历到兄弟节点时,将 return 掉当前调用,触发兄弟节点对应的 performUnitOfWork 逻辑;而遍历到父节点时,则会直接进入下一轮循环,也就是重复上两步的逻辑。
function completeUnitOfWork(unitOfWork: Fiber): void {
  let completedWork = unitOfWork;
  do {
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;

    /** 省略 **/
    
    next = completeWork(current, completedWork, subtreeRenderLanes);

    /** 省略 **/

    
    const siblingFiber = completedWork.sibling;
     // 若兄弟节点存在
    if (siblingFiber !== null) {
      // 将 workInProgress 赋值为当前节点的兄弟节点
      workInProgress = siblingFiber;
      // 将正在进行的 completeUnitOfWork 逻辑 return 掉
      return;
    }
    // 若兄弟节点不存在,completeWork 会被赋值为 returnFiber,也就是当前节点的父节点
    completedWork = returnFiber;
    // 这一步与上一步是相辅相成的,上下文中要求 workInProgress 与 completedWork 保持一致
    workInProgress = completedWork;
  } while (completedWork !== null);
}

小结

本章我们学习了render阶段completeWork流程,接下来的文章将进入diff源码分析,欢迎继续跟随本专栏一起学习;

参考链接

React源码系列