# expirationTime计算

expirationTime 时间的计算和 React 没有任何关联,首先让我们抛开 React 相关东西,单纯的来看 这个计算,在 /react-reconciler/src/ReactFiberExpirationTime.js 中我们可以看到相关的expirationTime计算 公式,代码如下

// MAX_SIGNED_31_BIT_INT 默认定义为 Math.pow(2, 30) - 1  1073741823
export const Sync = MAX_SIGNED_31_BIT_INT;
export const Batched = Sync - 1;


const UNIT_SIZE = 10;
const MAGIC_NUMBER_OFFSET = Batched - 1;  // 1073741821


// 1 unit of expiration time represents 10ms.
export function msToExpirationTime(ms: number): ExpirationTime {
  // Always add an offset so that we don't clash with the magic number for NoWork.
  return MAGIC_NUMBER_OFFSET - ((ms / UNIT_SIZE) | 0);
}


export function expirationTimeToMs(expirationTime: ExpirationTime): number {
  return (MAGIC_NUMBER_OFFSET - expirationTime) * UNIT_SIZE;
}


function ceiling(num: number, precision: number): number {
  return (((num / precision) | 0) + 1) * precision;
}


function computeExpirationBucket(
  currentTime,
  expirationInMs,
  bucketSizeMs,
): ExpirationTime {
  return (
    MAGIC_NUMBER_OFFSET -
    ceiling(
      MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE,
      bucketSizeMs / UNIT_SIZE,
    )
  );
}


export const LOW_PRIORITY_EXPIRATION = 5000;
export const LOW_PRIORITY_BATCH_SIZE = 250;


export function computeAsyncExpiration(
  currentTime: ExpirationTime,
): ExpirationTime {
  return computeExpirationBucket(
    currentTime,
    LOW_PRIORITY_EXPIRATION,
    LOW_PRIORITY_BATCH_SIZE,
  );
}


export function computeSuspenseExpiration(
  currentTime: ExpirationTime,
  timeoutMs: number,
): ExpirationTime {
  // TODO: Should we warn if timeoutMs is lower than the normal pri expiration time?
  return computeExpirationBucket(
    currentTime,
    timeoutMs,
    LOW_PRIORITY_BATCH_SIZE,
  );
}

export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;
export const HIGH_PRIORITY_BATCH_SIZE = 100;

export function computeInteractiveExpiration(currentTime: ExpirationTime) {
  return computeExpirationBucket(
    currentTime,
    HIGH_PRIORITY_EXPIRATION,
    HIGH_PRIORITY_BATCH_SIZE,
  );
}

React 有两种类型的 ExpirationTime, 一个是 Interactive,另一种是普通的异步。Interactive的比如说是由事件触发的,那么他的响应优先级会比较高因为涉及到交互

computeAsyncExpirationcomputeInteractiveExpiration 方法来说,他们分别调用 computeExpirationBucket 传入以下:

  • currentTime 调用 msToExpirationTime 得到的 ExpirationTime
  • expirationInMs 不同优先级任务会传不同的偏移量,把不同优先级的时间拉开差距
  • bucketSizeMs 越大,批处理的间隔就越大

我们现在套用公式来试试,

// computeAsyncExpiration 公式
computeExpirationBucket(currentTime,5000,250)
// computeExpirationBucket 方法
(1073741821-ceiling(1073741821-currentTime+5000/10,250/10))
// 代入 ceiling
1073741821-((((1073741821 - currentTime + 500) / 25) | 0) + 1) * 25

// 我们设定currentTime的值为 997到1025得出结果为
// 从997到1021的结果都是496
// 从1022到1025的结果都是521
// 可以得出再间隔25ms内即(1021 - 997 + 1)结果是一致的。
// 也即异步更新的过期时间间隔是25ms

其他的如 computeSuspenseExpirationcomputeInteractiveExpiration是同样的道理。

React为什么这么设计呢?

这么做也许是为了让非常相近的两次更新得到相同的expirationTime,然后在一次更新中完成,相当于一个自动的batchedUpdates。

我们知道setState可能会批量更新,就是这个原因。

内部会把异步更新间隔在25ms内的更新合并成一个,可以很大的提高性能

更新时间: 9/4/2020, 11:56:27 AM