10

React Time Slicing-ExpirationTime

 3 years ago
source link: https://zhuanlan.zhihu.com/p/65702480
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

React Time Slicing-ExpirationTime

阿里巴巴 前端开发工程师

ExpirationTime 在整个时间片的计算是非常重要的,也非常有意思。这块是相对独立一个功能模块,贯穿着整个 setState 的变更流程,甚至理解时间切片所带来的性能的意义至关重要。

v2-7e2ce079569c1fd17c2f94eb12716eaa_720w.jpg

可以从这个图上看到 CurrentTime 是计算 ExpirationTime 基础, CurrentTime 有方法 msToExpirationTime 计算而来。

export const NoWork = 0;
export const Sync = 1;
export const Never = MAX_SIGNED_31_BIT_INT;

const UNIT_SIZE = 10;
const MAGIC_NUMBER_OFFSET = 2;

// 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 ((ms / UNIT_SIZE) | 0) + MAGIC_NUMBER_OFFSET;
}

可以看到UNIT_SIZE为10,这样10秒内无论调用这个方法多少次,产生的 CurrentTime 的值是一样的,这样就磨平了10秒的 CurrentTime 值差异。MAGIC_NUMBER_OFFSET为2,只是添加一个偏移量,防止跟其他或是之前产生的值冲突。

react-reconciler 这个组件中 ReactFiberExpirationTime.js 文件总共 export 4个方法:

msToExpirationTime // 时间戳转换成 ExpirationTime
expirationTimeToMs // ExpirationTime 转换成时间戳
computeAsyncExpiration  //异步更新需要生成的 ExpirationTime
computeInteractiveExpiration // Interactive 更新需要 ExpirationTime

重点看下 computeAsyncExpiration 和 computeInteractiveExpiration 生成时间 ExpirationTime的流程。可以看下上图的生成流程。 两个方法都调用了 computeExpirationBucket 这个方法,只是传递的常量不同而已。看下 computeAsyncExpiration 方法的代码:

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

function computeExpirationBucket(
  currentTime,
  expirationInMs,
  bucketSizeMs,
): ExpirationTime {
  return (
    MAGIC_NUMBER_OFFSET +
    ceiling(
      currentTime - MAGIC_NUMBER_OFFSET + 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,
  );
}

拨丝抽茧之后得到的公式如下:

((((currentTime - 2 + 5000 / 10) / 25) | 0) + 1) * 25

其中250 / 10)/25)| 0的作用是取整的意思。 当前 currentTime 时间加上498然后处以25取整再加1再乘以 25,而除以 10 取整应该是要抹平 10 毫秒内的误差,当然最终要用来计算时间差的时候会调用expirationTimeToMs恢复回去,但是被取整去掉的 10 毫秒误差肯定是回不去的。使用这个公式计算下输入20002 - 20026之间,最终得到的结果都是20525,但是到了20027的到的结果就是20550,这就是除以25取整的效果,如果前后数据相差不大于25,那么最终得到的值就是相同的。

个人感觉这个地方的还是很精妙的,更觉的如何使用 ExpirationTime 在 setState 的过程实现更新会是更加精妙的事情,隐隐约约觉得有点像我们常说的函数节流。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK