React 源码讲解第 6 节- expirationTime 公式

React 源码讲解第 6 节- expirationTime 公式


本节主要讲解 expirationTime 时间计算公式。

入口

updateContainer

function updateContainer(element, container, parentComponent, callback) {
    
    
//···
  var currentTime = requestCurrentTimeForUpdate();
  var expirationTime = computeExpirationForFiber(currentTime, current$1, suspenseConfig);
//···
  return expirationTime;
}

currentTime 源码

requestCurrentTimeForUpdate

var NoWork = 0;
var currentEventTime = NoWork;
function requestCurrentTimeForUpdate() {
    
    
  if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
    
    
    // We're inside React, so it's fine to read the actual time.
    return msToExpirationTime(now());
  } // We're not inside React, so we may be in the middle of a browser event.


  if (currentEventTime !== NoWork) {
    
    
    // Use the same start time for all updates until we enter React again.
    return currentEventTime;
  } // This is the first update since React yielded. Compute a new start time.


  currentEventTime = msToExpirationTime(now());
  return currentEventTime;
}

currentTime 解析

  1. requestCurrentTimeForUpdate 方法中,currentTime 初始为 0,代表没有更新。
var NoWork = 0;
var currentEventTime = NoWork;
  1. 如果 currentTime 不为 0 直接返回,否则赋值 currentEventTime = msToExpirationTime(now())

msToExpirationTime

function msToExpirationTime(ms) {
    
    
  // Always subtract from the offset so that we don't clash with the magic number for NoWork.
  return MAGIC_NUMBER_OFFSET - (ms / UNIT_SIZE | 0);
}
function expirationTimeToMs(expirationTime) {
    
    
  return (MAGIC_NUMBER_OFFSET - expirationTime) * UNIT_SIZE;
}

now

var initialTimeMs = Scheduler_now();//系统当前时间
var now = initialTimeMs < 10000 ? Scheduler_now : function () {
    
    
  return Scheduler_now() - initialTimeMs;
};
var Scheduler_now = Scheduler.unstable_now,

如果初始时间 initialTimeMs 相当小,小于10s,则 now 为调度器 Scheduler 的 now 当前时间。否则 now 为 Scheduler 的当前时间减去初始时间 initialTimeMs。

MAGIC_NUMBER_OFFSET

var MAX_SIGNED_31_BIT_INT = 1073741823;//最大31位整数
var Sync = MAX_SIGNED_31_BIT_INT;//代表同步执行,不会被调度也不会被打断
var Batched = Sync - 1;//批量处理
var MAGIC_NUMBER_OFFSET = Batched - 1; //最大整数偏移量

由以上代码可以知道 MAGIC_NUMBER_OFFSET 为 1073741823-2 = 1073741821。

所以 currentTime 时间为 :

  1. 调度器 Scheduler 的 系统当前时间 now/10,抹去10ms 的误差。
  2. 以上结果 |0,取整。
  3. 最大偏移量 MAGIC_NUMBER_OFFSET- 以上结果。

即:1073741821 - (now/10|0)。

expirationTime 源码

function computeExpirationForFiber(currentTime, fiber, suspenseConfig) {
    
    
  var mode = fiber.mode;

  if ((mode & BlockingMode) === NoMode) {
    
    
    return Sync;
  }

  var priorityLevel = getCurrentPriorityLevel();

  if ((mode & ConcurrentMode) === NoMode) {
    
    
    return priorityLevel === ImmediatePriority ? Sync : Batched;
  }

  if ((executionContext & RenderContext) !== NoContext) {
    
    
    // Use whatever time we're already rendering
    // TODO: Should there be a way to opt out, like with `runWithPriority`?
    return renderExpirationTime$1;
  }

  var expirationTime;

  if (suspenseConfig !== null) {
    
    
    // Compute an expiration time based on the Suspense timeout.
    expirationTime = computeSuspenseExpiration(currentTime, suspenseConfig.timeoutMs | 0 || LOW_PRIORITY_EXPIRATION);
  } else {
    
    
    // Compute an expiration time based on the Scheduler priority.
    switch (priorityLevel) {
    
    
      case ImmediatePriority:
        expirationTime = Sync;
        break;

      case UserBlockingPriority$1:
        // TODO: Rename this to computeUserBlockingExpiration
        expirationTime = computeInteractiveExpiration(currentTime);
        break;

      case NormalPriority:
      case LowPriority:
        // TODO: Handle LowPriority
        // TODO: Rename this to... something better.
        expirationTime = computeAsyncExpiration(currentTime);
        break;

      case IdlePriority:
        expirationTime = Idle;
        break;

      default:
        {
    
    
          {
    
    
            throw Error( "Expected a valid priority level" );
          }
        }

    }
  } // If we're in the middle of rendering a tree, do not update at the same
  // expiration time that is already rendering.
  // TODO: We shouldn't have to do this if the update is on a different root.
  // Refactor computeExpirationForFiber + scheduleUpdate so we have access to
  // the root when we check for this condition.


  if (workInProgressRoot !== null && expirationTime === renderExpirationTime$1) {
    
    
    // This is a trick to move this update into a separate batch
    expirationTime -= 1;
  }

  return expirationTime;
}

expirationTime 解析

如果有使用 suspenseConfig,expirationTime = computeSuspenseExpiration(currentTime, suspenseConfig.timeoutMs | 0 || LOW_PRIORITY_EXPIRATION);

如果没有使用 suspenseConfig,React 中有五种类型的 ExpirationTime。

    switch (priorityLevel) {
    
    
      case ImmediatePriority:
        expirationTime = Sync;
        break;

      case UserBlockingPriority$2:
        // TODO: Rename this to computeUserBlockingExpiration
        expirationTime = computeInteractiveExpiration(currentTime);
        break;

      case NormalPriority:
      case LowPriority:
        // TODO: Handle LowPriority
        // TODO: Rename this to... something better.
        expirationTime = computeAsyncExpiration(currentTime);
        break;

      case IdlePriority:
        expirationTime = Idle;
        break;

      default:
        (function () {
    
    
          {
    
    
            {
    
    
              throw ReactError(Error("Expected a valid priority level"));
            }
          }
        })();

    }
  1. 立即执行 - 最高优先级 ImmediatePriority
    expirationTime = Sync
    //var MAX_SIGNED_31_BIT_INT = 1073741823;
    //var Sync = MAX_SIGNED_31_BIT_INT;
    即:expirationTime 为 1073741821
  2. 交互 - 次优先级 UserBlockingPriority$1
    expirationTime = computeInteractiveExpiration(currentTime)
    比如由事件触发,响应优先级也会比较高,因为涉及到用户交互。
  3. 同步 - 正常优先级 NormalPriority
    var expirationTime
    即:expirationTime 为 undefined。
  4. 异步 - 低优先级 LowPriority
    expirationTime = computeAsyncExpiration(currentTime)
    比如执行异步任务。
  5. 延迟 - 最低优先级 IdlePriority
    expirationTime = Idle
    // var Idle = 2
    即:expirationTime 为2。

次优先级computeInteractiveExpiration和 低优先级computeAsyncExpiration(currentTime) 都是调用 computeExpirationBucket 方法。区别在于常量 expirationInMs 和 bucketSizeMs 不同。

var HIGH_PRIORITY_EXPIRATION =  500 ;
var HIGH_PRIORITY_BATCH_SIZE = 100;
function computeInteractiveExpiration(currentTime) {
    
    
  return computeExpirationBucket(currentTime, HIGH_PRIORITY_EXPIRATION, HIGH_PRIORITY_BATCH_SIZE);
}

var LOW_PRIORITY_EXPIRATION = 5000;
var LOW_PRIORITY_BATCH_SIZE = 250;
function computeAsyncExpiration(currentTime) {
    
    
  return computeExpirationBucket(currentTime, LOW_PRIORITY_EXPIRATION, LOW_PRIORITY_BATCH_SIZE);
}

核心代码:

var MAX_SIGNED_31_BIT_INT = 1073741823;
var Sync = MAX_SIGNED_31_BIT_INT;
var Batched = Sync - 1;
var UNIT_SIZE = 10;
var MAGIC_NUMBER_OFFSET = Batched - 1; // 1 unit of expiration time represents 10ms.

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

function computeExpirationBucket(currentTime, expirationInMs, bucketSizeMs) {
    
    
  return MAGIC_NUMBER_OFFSET - ceiling(MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE, bucketSizeMs / UNIT_SIZE);
} // TODO: This corresponds to Scheduler's NormalPriority, not LowPriority. Update
// the names to reflect.

从以上函数中可以看出:
只有 currentTime 是变量,也就是当前的时间戳。如果是高优先级任务。
expirationInMs对应的是 HIGH_PRIORITY_EXPIRATION,也就是 5000。
bucketSizeMs 对应的是 HIGH_PRIORITY_BATCH_SIZE,也就是 250。
所以 computeExpirationBucket 函数接收的就是 currentTime、5000 和 250。

ceiling 函数:

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

ceiling 调用:

ceiling(MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE, bucketSizeMs / UNIT_SIZE)

ceiling 的参数结果为:

(MAGIC_NUMBER_OFFSET - currentTime + 5000 / 10, 250/ 10)

由于上面计算过 currentTime = MAGIC_NUMBER_OFFSET - (now/10|0)。
所以ceiling 的参数结果为:

(MAGIC_NUMBER_OFFSET - (MAGIC_NUMBER_OFFSET - (now/10|0))+ 5000 / 10, 250/ 10)

即最终 ceiling 的参数结果为:
((now/10|0))+ 500,25)

((num / precision | 0) + 1) * precision
ceiling 的结果公式如下:
(((now/10|0)+ 500)/25|0)+1)*25

expirationTime 最终的公式为:
MAGIC_NUMBER_OFFSET - (((now/10|0)+ 500)/25|0)+1)*25
即:1073741821-(((now/10|0)+ 500)/25|0)+1)*25。

其中:
25是250 / 10,表示以 25 为单位向上增加的,比如输入10002 ~ 10026 之间的数,最终得到的结果都是10525,但是到了 10027 得到的结果就是10550,这就是除以 25 取整的效果。

值得注意的是 msToExpirationTime 和 expirationTimeToMs 是相互转换的关系。有一点非常重要,那就是用来计算 expirationTime 的 currentTime 是通过 msToExpirationTime(now) 得到的,是进行预先处理过的。

猜你喜欢

转载自blog.csdn.net/weixin_44135121/article/details/108863397
今日推荐