2024-08-27 10:10:05 +08:00

4.9 KiB
Raw Blame History

beginWork工作流程

面试题beginWork 中主要做一些什么工作?整体的流程是怎样的?

Reconciler协调器 是 Render 阶段的第二阶段工作,整个工作的过程可以分为“递”和“归”:

  • beginWork
  • completeWork

image-20230224111517826

beginWork 方法主要是根据传入的 FiberNode 创建下一级的 FiberNode。

整个 beginWork 方法的流程如下图所示:

image-20230301095305141

首先在 beginWork 中,会判断当前的流程是 mount初次渲染还是update更新判断的依据就是 currentFiberNode 是否存在

if(current !== null){
  // 说明 CurrentFiberNode 存在,应该是 update
} else {
  // 应该是 mount
}

如果是 update接下来会判断 wipFiberNode 是否能够复用,如果不能够复用,那么 update 和 mount 的流程大体上一致:

  • 根据 wip.tag 进行不同的分支处理
  • 根据 reconcile 算法生成下一级的 FiberNodediff 算法)

无法复用的 update 流程和 mount 流程大体一致,主要区别在于是否会生成带副作用标记 flags 的 FiberNode

beginWork 方法的代码结构如下:

// current 代表的是 currentFiberNode
// workInProgress 代表的是 workInProgressFiberNode后面我会简称为 wip FiberNode
function beginWork(current, workInProgress, renderLanes) {
  // ...
  if(current !== null) {
    // 进入此分支,说明是更新
  } else {
    // 说明是首次渲染
  }
  
  // ...
  
  // 根据不同的 tag进入不同的处理逻辑
  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      // ...
    }
    case FunctionComponent : {
      // ...
    }
    case ClassComponent : {
      // ...
    }
  }
}

关于 tag在 React 源码中定义了 28 种 tag

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;
// ...

不同的 FiberNode会有不同的 tag

  • HostComponent 代表的就是原生组件div、span、p
  • FC 在 mount 的时候,对应的 tag 为 IndeterminateComponent在 update 的时候就会进入 FunctionComponent
  • HostText 表示的是文本元素

根据不同的 tag 处理完 FiberNode 之后根据是mount 还是 update 会进入不同的方法:

  • mountmountChildFibers
  • updatereconcileChildFibers

这两个方法实际上都是一个叫 ChildReconciler 方法的返回值:

var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);

function ChildReconciler(shouldTrackSideEffects) {}

也就是说,在 ChildReconciler 方法内容shouldTrackSideEffects 是一个布尔值

  • false不追踪副作用不做 flags 标记,因为你是 mount 阶段
  • true要追踪副作用做 flags 标记,因为是 update 阶段

在 ChildReconciler 方法内部,就会根据 shouldTrackSideEffects 做一些不同的处理:

function placeChild(newFiber, lastPlacedIndex, newIndex){
  newFiber.index = newIndex;
  
  if(!shouldTrackSideEffects){
    // 说明是初始化
    // 说明不需要标记 Placement
    newFiber.flags |= Forked;
    return lastPlacedIndex
  }
  // ...
  // 说明是更新
  // 标记为 Placement
  newFiber.flags |= Placement;
  
}

可以看到,在 beginWork 方法内部,也会做一些 flags 标记(主要是在 update 阶段),这些 flags 标记主要和元素的位置有关系:

  • 标记 ChildDeletion这个是代表删除操作
  • 标记 Placement这是代表插入或者移动操作

真题解答

题目beginWork 中主要做一些什么工作?整体的流程是怎样的?

参考答案:

在 beginWork 会根据是 mount 还是 update 有着不一样的流程。

如果当前的流程是 update则 WorkInProgressFiberNode 存在对应的 CurrentFiberNode接下来就判断是否能够复用。

如果无法复用 CurrentFiberNode那么 mount 和 update 的流程大体上是一致的:

  • 根据 wip.tag 进入“不同类型元素的处理分支”
  • 使用 reconcile 算法生成下一级 FiberNodediff 算法)

两个流程的区别在于“最终是否会为生成的子 FiberNode 标记副作用 flags”

在 beginWork 中,如果标记了副作用的 flags那么主要与元素的位置相关包括

  • 标记 ChildDeletion代表删除操作
  • 标记 Placement代表插入或移动操作