React16源码: React中更新阶段中不同类型的expirationTime之pendingTime,suspendedTime以及pingedTime的源码实现

不同类型的 expirationTime


1 )概述

  • 在 react 中有几种不同类型的 expirationTime
    • pendingTime
    • suspendedTime
    • pingedTime

2 )源码

2.1 关于 pendingTime

定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L1788

function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
  const root = scheduleWorkToRoot(fiber, expirationTime);
  // ... 跳过很多代码
  markPendingPriorityLevel(root, expirationTime); // 注意这里
  // ... 跳过很多代码
}
  • 所有创建更新都会通过调用 scheduleWork 来开始进行一个任务队列的调度的一个过程
  • 因为对于调用 scheduleWork 进来的任务,都认为它是 pending 的任务
  • pending 的任务就是一个等待渲染的一个任务, 这个任务因为没有经过任何的更新,没有经过像 suspend 这样的流程
  • 所以它是一个新创建的任务,是一个pending的任务

定位到 packages/react-reconciler/src/ReactFiberPendingPriority.js#L19

进入 markPendingPriorityLevel

export function markPendingPriorityLevel(
  root: FiberRoot,
  expirationTime: ExpirationTime,
): void {
  // If there's a gap between completing a failed root and retrying it,
  // additional updates may be scheduled. Clear `didError`, in case the update
  // is sufficient to fix the error.
  root.didError = false;

  // Update the latest and earliest pending times
  const earliestPendingTime = root.earliestPendingTime;
  if (earliestPendingTime === NoWork) {
    // No other pending updates.
    root.earliestPendingTime = root.latestPendingTime = expirationTime;
  } else {
    if (earliestPendingTime < expirationTime) {
      // This is the earliest pending update.
      root.earliestPendingTime = expirationTime;
    } else {
      const latestPendingTime = root.latestPendingTime;
      if (latestPendingTime > expirationTime) {
        // This is the latest pending update
        root.latestPendingTime = expirationTime;
      }
    }
  }
  findNextExpirationTimeToWorkOn(expirationTime, root); // 该函数用于确定下一个要处理的过期时间
}
  • 获取 root.earliestPendingTime
  • 如果 earliestPendingTime === NoWork 代表现在root上面没有正在等待更新的任务
    • 这时候 root.earliestPendingTime = root.latestPendingTime = expirationTime
    • 因为它是第一个
  • 如果有 earliestPendingTime 而且 earliestPendingTime < expirationTime
    • earliestPendingTime 表示最早的等待更新时间,取最大值,优先级最小
    • root.earliestPendingTime = expirationTime;
  • 如果 latestPendingTime > expirationTime
    • latestPendingTime 表示最晚的等待更新时间,取最小值, 优先级最大
    • root.latestPendingTime = expirationTime;
  • 之后,调用这个 findNextExpirationTimeToWorkOn 方法

2.2 关于 suspendedTime

定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L1433

下面是 renderRoot 中的一段代码

注意这里的 markSuspendedPriorityLevel

if (nextRenderDidError) {
  // There was an error
  if (hasLowerPriorityWork(root, expirationTime)) {
    // There's lower priority work. If so, it may have the effect of fixing
    // the exception that was just thrown. Exit without committing. This is
    // similar to a suspend, but without a timeout because we're not waiting
    // for a promise to resolve. React will restart at the lower
    // priority level.
    markSuspendedPriorityLevel(root, expirationTime); // 注意这里
    
    // ... 跳过很多代码

    return;
  } else if (
    // There's no lower priority work, but we're rendering asynchronously.
    // Synchronsouly attempt to render the same level one more time. This is
    // similar to a suspend, but without a timeout because we're not waiting
    // for a promise to resolve.
    !root.didError &&
    isYieldy
  ) {
    // ... 跳过很多代码
  }
}
  • hasLowerPriorityWork 表示对于有低优先级的任务,这时候调用 markSuspendedPriorityLevel, 进入它
    export function markSuspendedPriorityLevel(
      root: FiberRoot,
      suspendedTime: ExpirationTime,
    ): void {
      root.didError = false;
      clearPing(root, suspendedTime);
    
      // First, check the known pending levels and update them if needed.
      // 这俩值是用于记录 root 上面所有被挂起的任务,它的 expirationTime 的区间的
      const earliestPendingTime = root.earliestPendingTime;
      const latestPendingTime = root.latestPendingTime;
      if (earliestPendingTime === suspendedTime) {
        if (latestPendingTime === suspendedTime) {
          // Both known pending levels were suspended. Clear them.
          root.earliestPendingTime = root.latestPendingTime = NoWork;
        } else {
          // The earliest pending level was suspended. Clear by setting it to the
          // latest pending level.
          root.earliestPendingTime = latestPendingTime;
        }
      } else if (latestPendingTime === suspendedTime) {
        // The latest pending level was suspended. Clear by setting it to the
        // latest pending level.
        root.latestPendingTime = earliestPendingTime; // 这里是清理的工作,后续有新的 expirationTime 进来后进行新的设置
      }
    
      // 接下去才是真正的操作
      // Finally, update the known suspended levels.
      const earliestSuspendedTime = root.earliestSuspendedTime;
      const latestSuspendedTime = root.latestSuspendedTime;
      if (earliestSuspendedTime === NoWork) {
        // No other suspended levels.
        root.earliestSuspendedTime = root.latestSuspendedTime = suspendedTime;
      } else {
        if (earliestSuspendedTime < suspendedTime) {
          // This is the earliest suspended level.
          root.earliestSuspendedTime = suspendedTime;
        } else if (latestSuspendedTime > suspendedTime) {
          // This is the latest suspended level
          root.latestSuspendedTime = suspendedTime;
        }
      }
    
      findNextExpirationTimeToWorkOn(suspendedTime, root);
    }
    
    • 这个方法跟设置 pendingTime 的方法其实是差不多的
    • 这里不进行逐个解释, 主要功能是处理被挂起任务的 expirationTime 最早和最晚的区间的

2.3 pingedTime

定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L1656

retrySuspendedRoot 函数内执行了 markPingedPriorityLevel

function retrySuspendedRoot(
  root: FiberRoot,
  boundaryFiber: Fiber,
  sourceFiber: Fiber,
  suspendedTime: ExpirationTime,
) {
  let retryTime;

  if (isPriorityLevelSuspended(root, suspendedTime)) {
    // Ping at the original level
    retryTime = suspendedTime;

    markPingedPriorityLevel(root, retryTime); // 注意这里
  } else {
    // ... 跳过很多代码
  }

  // ... 跳过很多代码
}
  • 这个 retrySuspendedRoot 函数在 throw 了 Promise,并且resolve之后会调用这个方法

进入 markPingedPriorityLevel

export function markPingedPriorityLevel(
  root: FiberRoot,
  pingedTime: ExpirationTime,
): void {
  root.didError = false;

  // TODO: When we add back resuming, we need to ensure the progressed work
  // is thrown out and not reused during the restarted render. One way to
  // invalidate the progressed work is to restart at expirationTime + 1.
  const latestPingedTime = root.latestPingedTime;
  if (latestPingedTime === NoWork || latestPingedTime > pingedTime) {
    root.latestPingedTime = pingedTime; // 这里取最小值
  }
  findNextExpirationTimeToWorkOn(pingedTime, root);
}

现在发现,3种 expirationTime 的处理最终都调用了 findNextExpirationTimeToWorkOn

// packages/react-reconciler/src/ReactFiberPendingPriority.js#L248

function findNextExpirationTimeToWorkOn(completedExpirationTime, root) {
  const earliestSuspendedTime = root.earliestSuspendedTime;
  const latestSuspendedTime = root.latestSuspendedTime;
  const earliestPendingTime = root.earliestPendingTime;
  const latestPingedTime = root.latestPingedTime;

  // Work on the earliest pending time. Failing that, work on the latest
  // pinged time.
  // 对于 nextExpirationTimeToWorkOn 的赋值,存在 earliestPendingTime 则取其值,否则取 latestPingedTime 值
  let nextExpirationTimeToWorkOn =
    earliestPendingTime !== NoWork ? earliestPendingTime : latestPingedTime;

  // If there is no pending or pinged work, check if there's suspended work
  // that's lower priority than what we just completed.
  // 这时候还要判断下和 suspend 的关系
  // nextExpirationTimeToWorkOn === NoWork 代表了 earliestPendingTime 和 latestPingedTime 都是 NoWork
  if (
    nextExpirationTimeToWorkOn === NoWork &&
    (completedExpirationTime === NoWork ||
      latestSuspendedTime < completedExpirationTime)
  ) {
    // The lowest priority suspended work is the work most likely to be
    // committed next. Let's start rendering it again, so that if it times out,
    // it's ready to commit.
    nextExpirationTimeToWorkOn = latestSuspendedTime; // 设置优先级最高的
  }
  
  // 接下去是去设置 expirationTime
  let expirationTime = nextExpirationTimeToWorkOn;
  if (expirationTime !== NoWork && earliestSuspendedTime > expirationTime) {
    // Expire using the earliest known expiration time.
    expirationTime = earliestSuspendedTime;
  }

  root.nextExpirationTimeToWorkOn = nextExpirationTimeToWorkOn;
  root.expirationTime = expirationTime;
}
  • 这里可以看到 expirationTime 和 nextExpirationTimeToWorkOn 的区别在于
    • 如果有 earliestSuspendedTime 并且比 expirationTime 要大, 即 earliestSuspendedTime的优先级较低
      • 则 expirationTime 取 earliestSuspendedTime,取优先级低的
    • 还有,就是比如在 renderRoot 里面,手动设置 root.expirationTime = Sync 来发起一个同步的更新任务
      • 这就是 expirationTime 的作用
  • 综上,目前大致了解了在root上面有这三种不同的值来记录不同场景产生的更新中相关 time 的一个范围

你可能感兴趣的:(React,React,Native,react.js,前端,前端框架)