React源码学习进阶(四)render流程的入口逻辑详解

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

源码解析

接下来我们分析一下updateContainer的逻辑,它的入口在packages/react-reconciler/src/ReactFiberReconciler.js中:

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): ExpirationTime {
  
  const current = container.current;
  const currentTime = requestCurrentTimeForUpdate();

  const suspenseConfig = requestCurrentSuspenseConfig();
  const expirationTime = computeExpirationForFiber(
    currentTime,
    current,
    suspenseConfig,
  );

  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  const update = createUpdate(expirationTime, suspenseConfig);
  // Caution: React DevTools currently depends on this property
  // being called "element".
  update.payload = {element};

  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    if (__DEV__) {
      if (typeof callback !== 'function') {
        console.error(
          'render(...): Expected the last optional `callback` argument to be a ' +
            'function. Instead received: %s.',
          callback,
        );
      }
    }
    update.callback = callback;
  }

  enqueueUpdate(current, update);
  scheduleWork(current, expirationTime);

  return expirationTime;
}

这段代码里面有几个非常关键的信息:

  • 通过computeExpirationForFiber计算出的expirationTime,这个涉及到调度优先级,我们暂时可以认为它就是Sync的常量值。

  • 调用createUpdate创建了一个update,并调用enqueueUpdateupdate入队。

  • 调用scheduleWork启动整个更新流程。

接下来先看一下createUpdate的逻辑,源码位于packages/react-reconciler/src/ReactUpdateQueue.js

可以看到,update其实就是一个环状链表的结构,自己指向自己:

image-20220915125958637

然后通过enqueueUpdate方法将update放入之前fiberupdateQueue里:

实际上此时的update被存储在updateQueueshared.pending字段中。

scheduleWork启动

scheduleWork的启动代码位于packages/react-reconciler/src/ReactFiberWorkLoop.js中:

这段代码实际上核心关注两个点:

  • 调用markUpdateTimeFromFiberToRoot获取root,在这个过程中会更新FiberexpirationTime

  • 调用performSyncWorkOnRoot,开始执行work的逻辑。

首先看一下markUpdateTimeFromFiberToRoot的逻辑:

这里其实在root挂载时,只更新了当前的rootFiber,以及调用markRootUpdatedAtTime更新了root的firstPendingTime

接下来就是执行performSyncWorkOnRoot的逻辑了:

这个函数实际上才是真正的挂载更新入口,排除掉其他干扰,核心逻辑有三点:

  • 调用prepareFreshStack,初始化workInProgress的信息

  • 调用workLoopSync,启动render流程

  • 调用finishSyncRender,启动commit流程

首先我们看一下prepareFreshStack做了什么:

可以看到在这里创建了一个workInProgress,它的创建方法位于packages/react-reconciler/src/ReactFiber.js

实际上创建workInProgress的过程可以简单理解为将原来的fiber拷贝了一份。

接着我们来看看重头戏workLoopSync启动render流程:

这里是我们接触到的第一层循环,实际上这里的workInProgress会被不断改变,赋值为performUnitOfWork的返回值,而performUnitOfWork每次则会返回子节点或兄弟节点,从而开启整个Fiber树的遍历迭代流程。

最后我们再来看看performUnitOfWork的逻辑:

这段逻辑的核心在于两个点:

  • 调用beginWork处理节点,拿到下一个节点

  • 如果下一个节点不存在,则调用completeUnitOfWork处理节点,并拿到下一个节点

请记住这里的beginWorkcompleteUnitOfWork的调用逻辑,实际上它们在遍历过程中扮演着进入节点和离开节点所做的工作,后面我们将围绕这两个核心Fiber操作展开详细的讲解。

小结一下

整体在进入正式的render流程之前,React Fiber本身其实做了大量工作,从入口处更能理清整个挂载更新过程的主体脉络:

image-20220915202823214

在有了这个脉络之后,我们就可以把重点放在三个方向上:

  • beginWork具体做了什么事情?(实际上它很重要,整个reconciler都是这个阶段完成)

  • completeWork具体做了什么事情?

  • commit具体做了什么事情?

搞懂了这三个方面,我们就知道在Fiber架构下,React是如何进行渲染更新的了。

Last updated