React18.2源码解析之render阶段(中)

198 阅读8分钟

记录学习过程,如有错误欢迎指出

书接上文,这篇文章讲解beginWork和completeWork

开始之前(必看)

React version 18.2.0

DEV代码可以忽略,下面正文我使用省略号就代表是dev相关的代码,包含hydrate字样的也请忽略,那是服务端渲染相关,望周知

我使用深度优先(🐶)的方式讲解:即遇到函数先进入函数,执行完函数后,又退回之前的函数.而不是在一个函数中讲完了再讲函数中执行的函数(希望能听懂我在说什么^v^)

因为使用了深度优先的方式讲解,耦合比较重,不建议跳着看

performUnitOfWork(未完)

不管是renderRootSync还是renderRootConcurrent最终都是进入了performUnitOfWork函数,接下来我们就看看performUnitOfWork内有哪些操作

function performUnitOfWork(unitOfWork: Fiber /*workInProgress */): void {
  // The current, flushed, state of this fiber is the alternate. Ideally
  // nothing should rely on this, but relying on it here means that we don't
  // need an additional field on the work in progress.
  // unitOfWork.alternate是节点副本和缓存树对象是一样的
  const current = unitOfWork.alternate;
  setCurrentDebugFiberInDEV(unitOfWork);

  let next;
  // export const ProfileMode = /*                    */ 0b000010;
  // export const NoMode = /*                         */ 0b000000;
  // 0b000000转换为boolean值是false

  // 无论如何都会执行beginWork()的
  // 只不过如果是true的情况会记录时间
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    //renderLanes = NoLanes
    // 递
    //进入beginWork()
    next = beginWork(current, unitOfWork, renderLanes);
    // 停止profiler计时器
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork(current, unitOfWork, renderLanes);
  }

  .....

beginWork

let beginWork;
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
  const dummyFiber = null;
  // 这里的beginWork相当于一个包装器
  beginWork = (current, unitOfWork, lanes /*Nolane */) => {
    // 如果一个组件抛出了一个错误,我们会在一个同步派发的事
    // 件中再次重放它,这样调试器就会把它当作一个未捕获的错误来处理

    // 在进入开始阶段之前,把正在进行的工作复制到一个假纤维上。如果beginWork抛出,我们将用它来重置状态。
    const originalWorkInProgressCopy = assignFiberPropertiesInDEV(
      dummyFiber,
      unitOfWork
    );
    try {
      // 真正的beginWork()
      return originalBeginWork(current, unitOfWork, lanes);

    //beginWork()出现错误才执行,没有出现错误就返回上一层函数了
    } catch (originalError) {
      if (
        didSuspendOrErrorWhileHydratingDEV() ||
        (originalError !== null &&
          typeof originalError === "object" &&
          typeof originalError.then === "function")
      ) {
        throw originalError;
      }
      
      // 重置语境依赖
      resetContextDependencies();
      resetHooksAfterThrow();
      // Don't reset current debug fiber, since we're about to work on the
      // same fiber again.

      // Unwind the failed stack frame
      unwindInterruptedWork(current, unitOfWork, workInProgressRootRenderLanes);

      // 将在3119行执行的assignFiberPropertiesInDEV恢复之前的状态
      assignFiberPropertiesInDEV(unitOfWork, originalWorkInProgressCopy);

      if (enableProfilerTimer && unitOfWork.mode & ProfileMode) {
        // Reset the profiler timer.
        startProfilerTimer(unitOfWork);
      }

      // 再一次执行beginWork()
      // invokeGuardedCallback()帮助收集错误
      invokeGuardedCallback(
        null,
        originalBeginWork,
        null,
        current,
        unitOfWork,
        lanes
      );

      ......
    }
  };
} else {
  beginWork = originalBeginWork;
}

真正的beginWork

beginWork分为两个判断一个update时,一个mount时,会执行不同的逻辑

update时

// 真正的beginWork()
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  .....

  // 即mount时current === null
  // 组件update时,由于之前已经mount过,所以current !== null。
  // 所以我们可以通过current === null ?来区分组件是处于mount还是update
  // update时:如果current存在可能存在优化路径,可以复用current(即上一次更新的Fiber节点)

  // update时
  if (current !== null) {
    const oldProps = current.memoizedProps;// 上一次更新的props
    const newProps = workInProgress.pendingProps; // 本次更新的props

    // didReceiveUpdate  表示是否有新的props更新,有则会设置为true,没有则是false
    if (
      oldProps !== newProps ||
      /*
        hasLegacyContextChanged判断了是否有老版本context使用并且发生变化
        react源码中存在一个valueStack和valueCursor用来记录context的历史信息和当前context,
        另外还有一个didPerformWorkStackCursor用来表示当前的context有没有变化
        hasLegacyContextChanged就会返回didPerformWorkStackCursor
      */
      hasLegacyContextChanged() ||
      // Force a re-render if the implementation changed due to hot reload:
      (__DEV__ ? workInProgress.type !== current.type : false)
    ) {(memo).
      didReceiveUpdate = true;
    } else {
      // checkScheduledUpdateOrContext函数检查当前fiber节点上的lanes是否存在于renderLanes中
      // 存在则说明当前fiber节点需要更新,不存在则不需要更新则复用之前的节点
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes,
      );
      if (
        !hasScheduledUpdateOrContext &&
        (workInProgress.flags & DidCapture) === NoFlags
      ) {
        didReceiveUpdate = false;
        // 复用之前的节点
        return attemptEarlyBailoutIfNoScheduledUpdate(
          current,
          workInProgress,
          renderLanes,
        );
      }
      if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
        didReceiveUpdate = true;
      } else {
        didReceiveUpdate = false;
      }
    }
  }

mount时

  else {
    // mount时
    didReceiveUpdate = false;

    if (getIsHydrating() && isForkedChild(workInProgress)) {
      const slotIndex = workInProgress.index;
      const numberOfForks = getForksAtLevel(workInProgress);
      pushTreeId(workInProgress, numberOfForks, slotIndex);
    }
  }

后续

后续就是对不同类型的节点进行判断

workInProgress.lanes = NoLanes;

  // 首先会根据不同 Fiber 节点的 tag,执行不同的 case,进入不同类型的 Fiber 子节点创建逻辑
  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      return mountIndeterminateComponent(
        current,
        workInProgress,
        workInProgress.type,
        renderLanes,
      );
    }
    case LazyComponent: {
      const elementType = workInProgress.elementType;
      return mountLazyComponent(
        current,
        workInProgress,
        elementType,
        renderLanes,
      );
    }
    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,
        renderLanes,
      );
    }
    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,
        renderLanes,
      );
    }
    
    
    .......
    
    
  );
}

performUnitOfWork(完)

next===null,说明comleteWork只会在beginWork完所以节点之后才会执行到

// 收集props
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // 如果没有了新的任务,就执行 "归" 操作
    // 归
    
    //进入completeUnitOfWork
    completeUnitOfWork(unitOfWork);
  } else {
    // 反之继续 执行beginWork()
    workInProgress = next;
  }

  ReactCurrentOwner.current = null;
}

completeUnitOfWork(完)

function completeUnitOfWork(unitOfWork: Fiber): void {
  // 尝试完成当前单位的工作,然后转到下一个,兄弟节点。如果没有更多的兄弟节点,则返回到父fiber。
  let completedWork = unitOfWork;
  do {
    // The current, flushed, state of this fiber is the alternate. Ideally
    // nothing should rely on this, but relying on it here means that we don't
    // need an additional field on the work in progress.
    // 获取备份节点中的当前节点
    const current = completedWork.alternate;
    // 获取父节点 return就是指向父节点的
    const returnFiber = completedWork.return;

    // Check if the work completed or if something threw.

    // Incomplete = 0b00000000001000000000000000;

    // 没有异常
    // flags就是标记,类似lane,也是级别
    if ((completedWork.flags & Incomplete) === NoFlags) {
      setCurrentDebugFiberInDEV(completedWork);
      let next;

      // 没有启动性能记录
      if (
        !enableProfilerTimer ||
        (completedWork.mode & ProfileMode) === NoMode
      ) {
        // 执行 completeWork
        next = completeWork(current, completedWork, renderLanes);
      } else {
      // 反之开启性能记录
        startProfilerTimer(completedWork);
        // 再执行completeWork
        // 在执行完completeWork()后next就是更新完后的节点
        next = completeWork(current, completedWork, renderLanes);
        // Update render duration assuming we didn't error.
        stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
      }

      resetCurrentDebugFiberInDEV();

      // 如果next存在,则表示产生了新 work
      if (next !== null) {
        // Completing this fiber spawned new work. Work on that next.

        // 将workInProgress替换为新的树,以便后续更新
        workInProgress = next;
        return;
      }
    // 若是该 fiber 节点未能完成 work 的话(异常)
    } else {
      // This fiber did not complete because something threw. Pop values off
      // the stack without entering the complete phase. If this is a boundary,
      // capture values if possible.

      // 因为节点未能完成更新,我们需要找到其中的错误
      const next = unwindWork(current, completedWork, renderLanes);

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

      // 如果next存在,则表示产生了新 work
      if (next !== null) {
        // If completing this work spawned new work, do that next. We'll come
        // back here again.
        // Since we're restarting, remove anything that is not a host effect
        // from the effect tag.

        // HostEffectMask = 0b00000000000111111111111111
        next.flags &= HostEffectMask;
        // 这里的逻辑和2076行一致,用新的替换旧的,就不需要return next出去了
        workInProgress = next;
        return;
      }

      // 由于该 fiber 未能完成,所以不必重置它的 expirationTime
      if (
        enableProfilerTimer &&
        (completedWork.mode & ProfileMode) !== NoMode
      ) {
        // Record the render duration for the fiber that errored.
        stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);

        //虽然报错了,但仍然会累计 work 时长,但不重置expirationTime
        let actualDuration = completedWork.actualDuration;
        let child = completedWork.child;
        while (child !== null) {
          actualDuration += child.actualDuration;
          child = child.sibling;
        }
        completedWork.actualDuration = actualDuration;
      }

      // 如果父节点存在的话,标记为「未完成」,并清除子树标记
      if (returnFiber !== null) {
        returnFiber.flags |= Incomplete;
        returnFiber.subtreeFlags = NoFlags;
        returnFiber.deletions = null;
      } else {
        // We've unwound all the way to the root.

        // workInProgressRootExitStatus是缓存树退出时标记的状态
        // RootDidNotComplete = 6
        workInProgressRootExitStatus = RootDidNotComplete;
        // 因为returnFiber不存在了,说明是root了,所以将缓存树清空,以便进行下一次更新
        // 这里置为null的原因是,在workLoopSync()内while执行的条件是workInProgress !== null,那么置为null就结束while了
        workInProgress = null;
        return;
      }
    }

    // 获取兄弟节点
    const siblingFiber = completedWork.sibling;
    // 如果兄弟节点存在,那么将缓存树替换为siblingFiber
    if (siblingFiber !== null) {
      // If there is more work to do in this returnFiber, do that next.
      workInProgress = siblingFiber;
      return;
    }
    //如果能执行到这一步的话,说明 siblingFiber 为 null,
    //那么就返回至父节点
    completedWork = returnFiber;
    // Update the next thing we're working on in case something throws.
    workInProgress = completedWork;
    // 只要completedWork不为null,那么就会一直更新,直到更新完毕
  } while (completedWork !== null);

  // 走到这里说明已经执行到root了,判断缓存树退出状态是否RootInProgress(0)
  if (workInProgressRootExitStatus === RootInProgress) {
    // 如果是那么标记退出状态为RootCompleted(5) 即[已完成]
    workInProgressRootExitStatus = RootCompleted;
  }
}

可以看到completeWork成功执行完后会带着ExitStatus返回,这也让我们和之前performConcurrentWorkOnRoot对上了

//performConcurrentWorkOnRoot

let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);

performConcurrentWorkOnRoot(完)

对completeWork退出状态进行相应处理

 .....
// 检查退出状态
  if (exitStatus !== RootInProgress) {
    // 是否出错
    if (exitStatus === RootErrored) {
      const errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);
      if (errorRetryLanes !== NoLanes) {
        lanes = errorRetryLanes;
        exitStatus = recoverFromConcurrentError(root, errorRetryLanes);
      }
    }
    // 是否是死亡了
    if (exitStatus === RootFatalErrored) {
      const fatalError = workInProgressRootFatalError;
      prepareFreshStack(root, NoLanes);
      markRootSuspended(root, lanes);
      ensureRootIsScheduled(root, now());
      throw fatalError;
    }

    // 是否没有完成
    if (exitStatus === RootDidNotComplete) {
      markRootSuspended(root, lanes);
    // 最后那就是渲染完成进入
    } else {
      const renderWasConcurrent = !includesBlockingLane(root, lanes);
      const finishedWork: Fiber = (root.current.alternate: any);

      if (
        renderWasConcurrent &&
        // isRenderConsistentWithExternalStores()就是判断store状态是否一致
        // Concurrent模式下,需要进行 store 的一致性检查
        !isRenderConsistentWithExternalStores(finishedWork)
      ) {
        // 如果Concurrent模式下,store状态不一致,那么进行同步渲染
        exitStatus = renderRootSync(root, lanes);

        // 再次判断是否出错
        if (exitStatus === RootErrored) {
          const errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);
          if (errorRetryLanes !== NoLanes) {
            lanes = errorRetryLanes;
            exitStatus = recoverFromConcurrentError(root, errorRetryLanes);
            // We assume the tree is now consistent because we didn't yield to any
            // concurrent events.
          }
        }
        // 再次判断是否死亡
        if (exitStatus === RootFatalErrored) {
          const fatalError = workInProgressRootFatalError;
          prepareFreshStack(root, NoLanes);
          markRootSuspended(root, lanes);
          ensureRootIsScheduled(root, now());
          throw fatalError;
        }
      }

      // 我们现在有一个一致的树。下一步是提交,或者,如果有事情暂停,则等待超时后再提交。
      root.finishedWork = finishedWork;
      root.finishedLanes = lanes;
      
      //!!!!!!
      // commit入口
      
      finishConcurrentRender(root, exitStatus, lanes);
      
      //!!!!!!
    }
  }
  // 再次进入ensureRootIsScheduled,确保根调度
  ensureRootIsScheduled(root, now());
  
  if (root.callbackNode === originalCallbackNode) {
    // 为这个根安排的任务节点是目前正在执行的那个,需要返回一个 continuation.
    return performConcurrentWorkOnRoot.bind(null, root);
  }
  return null;
}

总结

render阶段到此为止就结束了,下一阶段就是commit阶段了,入口位于performConcurrentWorkOnRoot的最后一段finishConcurrentRender(root, exitStatus, lanes);,当然这篇文章因为篇幅有限,没有讲到双缓存和diff过程,等把commit阶段讲完后,我会将双缓存树和diff单独讲一下,但是大家在注释中应该能看见在哪开启双缓存树的

我也画完了React三个阶段流程图,后面我会上传到github,欢迎大家star

记录学习过程,如有错误欢迎指出