共计 3311 个字符,预计需要花费 9 分钟才能阅读完成。
React 源码解析系列文章欢迎阅读:
React16 源码解析 (一)- 图解 Fiber 架构
React16 源码解析(二)- 创建更新
React16 源码解析(三)-ExpirationTime
React16 源码解析(四)-Scheduler
React16 源码解析(五)- 更新流程渲染阶段 1
React16 源码解析(六)- 更新流程渲染阶段 2
React16 源码解析(七)- 更新流程渲染阶段 3
React16 源码解析(八)- 更新流程提交阶段
正在更新中 …
在我的上篇文章中,ReactDOM.render 过程中的 updateContainer 函数里面有个计算到期时间的函数:
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
const current = container.current;
const currentTime = requestCurrentTime();
// 这里传入了 currentTime 和当前的 Fiber 对象调用了这个计算 expirationTime 的函数
const expirationTime = computeExpirationForFiber(currentTime, current);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}
在上篇文章讨论的 setState 和 forceUpdate 中同样的也要计算 expirationTime,这篇文章就来分析 expirationTime 是如何计算的。
为什么需要 ExpirationTime?
React16 带来的最振奋人心的改动就是 Fiber 架构,改变了之前 react 的组件渲染机制,新的架构使原来同步渲染的组件现在可以异步化,可中途中断渲染,执行更高优先级的任务。释放浏览器主线程。
所以每一个任务都会有一个优先级,不然岂不是会乱套了 ….. ExpirationTime 就是优先级,它是一个过期时间。
computeExpirationForFiber
在计算 ExpirationTime 之前调用了 requestCurrentTime 得到了一个 currentTime。这个函数里面牵扯了一些复杂的关于后面知识的逻辑,我们先不深究,大家就先理解为一个当前时间类似的概念。
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
let expirationTime;
// ......
if (fiber.mode & ConcurrentMode) {if (isBatchingInteractiveUpdates) {
// 交互引起的更新
expirationTime = computeInteractiveExpiration(currentTime);
} else {
// 普通异步更新
expirationTime = computeAsyncExpiration(currentTime);
}
}
// ......
}
// ......
return expirationTime;
}
在异步更新中,这里我们看到有两种计算更新的方式。computeInteractiveExpiration 和 computeAsyncExpiration
computeInteractiveExpiration
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,//150
HIGH_PRIORITY_BATCH_SIZE,//100
);
}
computeAsyncExpiration
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,//5000
LOW_PRIORITY_BATCH_SIZE,//250
);
}
computeExpirationBucket
查看上面两种方法,我们发现其实他们调用的是同一个方法:computeExpirationBucket,只是传入的参数不一样,而且传入的是常量。computeInteractiveExpiration 传入的是 150、100,computeAsyncExpiration 传入的是 5000、250。说明前者的优先级更高。那么我把前者称为高优先级更新,后者称为低优先级更新。
下面来看 computeExpirationBucket 方法的具体内容:
const UNIT_SIZE = 10;
const MAGIC_NUMBER_OFFSET = 2;
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,
)
);
}
看完之后,一脸懵。它在搞什么?别急,我们把公式整理一下:
以低优先级更新为例,最终的公式是:((((currentTime – 2 + 5000 / 10) / 25) | 0) + 1) * 25
其中只有只有 currentTime 是变量。
我们可以多试几个值看看:
((((101 - 2 + 5000 / 10) / 25) | 0) + 1) * 25 // 600
((((102 - 2 + 5000 / 10) / 25) | 0) + 1) * 25 // 625
((((105 - 2 + 5000 / 10) / 25) | 0) + 1) * 25 // 625
((((122 - 2 + 5000 / 10) / 25) | 0) + 1) * 25 // 625
((((126 - 2 + 5000 / 10) / 25) | 0) + 1) * 25 // 625
((((127 - 2 + 5000 / 10) / 25) | 0) + 1) * 25 // 650
简单来说,最终结果是以 25 为单位向上增加的,比如说我们输入 102 – 126 之间,最终得到的结果都是 625,但是到了 127 得到的结果就是 650 了,这就是除以 25 取整的效果。
即,低优先级更新的 expirationTime 间隔是 25ms,抹平了 25ms 内计算过期时间的误差,React 让两个相近(25ms 内)的得到 update 相同的 expirationTime,目的就是让这两个 update 自动合并成一个 Update,从而达到批量更新。
注:这里如果用高优先级更新去尝试多组数据,你会发现 expirationTime 间隔是 10ms。
任世界纷繁复杂, 仍旧保持可爱。
我是小柚子小仙女。文章如有不妥,欢迎指正~