React源码学习进阶(五)beginWork如何处理Fiber

本文采用React v16.13.1版本源码进行分析

源码解析

beginWork的代码位于packages/react-reconciler/src/ReactFiberBeginWork.js:

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const updateExpirationTime = workInProgress.expirationTime;
    
  // Before entering the begin phase, clear pending update priority.
  // TODO: This assumes that we're about to evaluate the component and process
  // the update queue. However, there's an exception: SimpleMemoComponent
  // sometimes bails out later in the begin phase. This indicates that we should
  // move this assignment out of the common path and into each branch.
  workInProgress.expirationTime = NoWork;

  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      return mountIndeterminateComponent(
        current,
        workInProgress,
        workInProgress.type,
        renderExpirationTime,
      );
    }
    case LazyComponent: {
      const elementType = workInProgress.elementType;
      return mountLazyComponent(
        current,
        workInProgress,
        elementType,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case ClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderExpirationTime);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderExpirationTime);
    case HostText:
      return updateHostText(current, workInProgress);
    case SuspenseComponent:
      return updateSuspenseComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case HostPortal:
      return updatePortalComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case ForwardRef: {
      const type = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === type
          ? unresolvedProps
          : resolveDefaultProps(type, unresolvedProps);
      return updateForwardRef(
        current,
        workInProgress,
        type,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case Fragment:
      return updateFragment(current, workInProgress, renderExpirationTime);
    case Mode:
      return updateMode(current, workInProgress, renderExpirationTime);
    case Profiler:
      return updateProfiler(current, workInProgress, renderExpirationTime);
    case ContextProvider:
      return updateContextProvider(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case ContextConsumer:
      return updateContextConsumer(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case MemoComponent: {
      const type = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      // Resolve outer props first, then resolve inner props.
      let resolvedProps = resolveDefaultProps(type, unresolvedProps);
      if (__DEV__) {
        if (workInProgress.type !== workInProgress.elementType) {
          const outerPropTypes = type.propTypes;
          if (outerPropTypes) {
            checkPropTypes(
              outerPropTypes,
              resolvedProps, // Resolved for outer only
              'prop',
              getComponentName(type),
              getCurrentFiberStackInDev,
            );
          }
        }
      }
      resolvedProps = resolveDefaultProps(type.type, resolvedProps);
      return updateMemoComponent(
        current,
        workInProgress,
        type,
        resolvedProps,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    case SimpleMemoComponent: {
      return updateSimpleMemoComponent(
        current,
        workInProgress,
        workInProgress.type,
        workInProgress.pendingProps,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    case IncompleteClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return mountIncompleteClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case SuspenseListComponent: {
      return updateSuspenseListComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
    case FundamentalComponent: {
      if (enableFundamentalAPI) {
        return updateFundamentalComponent(
          current,
          workInProgress,
          renderExpirationTime,
        );
      }
      break;
    }
    case ScopeComponent: {
      if (enableScopeAPI) {
        return updateScopeComponent(
          current,
          workInProgress,
          renderExpirationTime,
        );
      }
      break;
    }
    case Block: {
      if (enableBlocksAPI) {
        const block = workInProgress.type;
        const props = workInProgress.pendingProps;
        return updateBlock(
          current,
          workInProgress,
          block,
          props,
          renderExpirationTime,
        );
      }
      break;
    }
  }
}

实际上beginWork就是一个很大的分发入口,根据tag的不同来分发处理不同的逻辑,对于rootFiber来说,它的tag类型是HostRoot,因此我们先核心聚焦于HostRoot的实现:

我们先忽略前面对于contextupdateQueue的处理,实际上这里只有一个核心逻辑:

  • 调用reconcileChildren

  • 返回workInProgress.child

而实际上workInProgress.child正是reconciileChildren方法被赋值:

实际上挂载的场景执行的是reconcileChildFibers,这个实现在packages/react-reconciler/src/ReactChildFiber.js

实际上这里是根据elementType来进行不同的处理,我们常规来说应该是进入到reconcileSingleElement分支:

这里其实就是调用createFiberFromElement创建一个fiber节点返回,而这个节点正是rootFiber的子节点。

我们先回想一下,这个过程实际上伴随着这个return也就结束了,也就是在这个流程中,我们为rootFiber添加了一个child节点,形成链表结构,然后返回,到我们最开始的循环:

实际上,针对子节点,又会进入到performUnitOfWork流程中,继续执行beginWork流程。

也就是说,这个loop其实是在不断地构建整个Fiber树,同时在遍历处理每个Fiber节点的过程。

接下来处理的Fiber节点是个Mode节点,因为我们调用了React.StrictMode组件,对于这个节点的处理非常简单:

实际上它什么都没有做,就是继续递归执行reconcileChildren,不过它已经被插入到fiber树当中了,这个很重要。

要注意的是,这里的fiber其实是没有current的,实际上我们新创建的节点,并没有赋值alternate属性,自然也只有workInProgress而没有current,在没有current的情况下,reconcileChildren会走到mountChildFibers分支:

实际上这两个分支的区别只是在于传参的不同,这个参数是shouldTrackSideEffects,当我们调用mountChildFibers时,这个参数是false,如果是reconcile,则这个值是true,这一点我们先记下。

接下来执行的节点是App,它被标记成一个IndeterminateComponent,为什么会是这个组件呢?让我们看看createFiberFromElement的逻辑:

这里没什么特殊的处理,最终转化到createFiberFromTypeAndProps

从这里的逻辑就可以看到,如果我们正常一个组件进来创建Fiber的时候,默认就会标记为IndeterminateComponent,类组件会被标记为ClassComponent.

接着我们看一下这个组件的update流程:

这里核心做了两件事情:

  • 调用renderWithHooks对当前组件进行reconcile,拿到子节点

  • 标记Fiber节点为FunctionComponent,启动子节点的递归reconcileChildren

可以看到,针对IndeterminateComponent来说,一开始是不太明确它到底是个什么组件的,执行完了之后才赋值的FunctionComponent,这是因为类组件可以不使用类来实现(通过函数返回一个对象来实现),这是一个小众用法。

接下来我们简单看一下renderWithHooks的逻辑:

我们先忽略一下这里针对hooks的处理逻辑,其实可以简单的理解这里就是执行了函数组件,拿到了children,然后返回。

接着就是到了叶子组件HostComponent的更新流程了:

可以看到这里基本上没有什么处理逻辑,直接进入到reconcileChildren流程了,接下来的child子节点可能是一个数组:

从之前的分析就可以看出来,这块也是实现diff算法的精华所在,在这里我们先关注首次挂载的逻辑,会进到这个循环中:

这段代码做了三件事:

  • 调用createChild为每个子元素创建一个fiber

  • 调用placeChild确定放置的位置

  • 创建链表结构,sibling指向下一个兄弟节点

其中,placeChild逻辑如下:

上面提到了shouldTrackSideEffects在首次挂载的情况下是false,因此在挂载流程中这里是不执行任何逻辑的。

最后返回第一个child节点,这个单元逻辑就已经执行完成。

当下一个节点为空时,就会走到completeUnitOfWork的流程,这个流程下篇文章我们进行详细讲解。

至此,一次beginWork的深度递归就已经结束了。

小结一下

这次我们分析了一次beginWork递归的流程,其实beginWork就是一个用来分发和创建fiber树结构的入口,我们目前遇到了几类更新方法:

  • mountIndeterminateComponent,用来更新函数组件

  • updateHostRoot,用来更新根节点

  • updateHostComponent,用来更新DOM组件

  • updateMode用来更新Mode

在整个递归过程中,我们创建了如下的Fiber树:

image-20220917145505610

Last updated