我们解析的源码是 React18.1.0 版本,请注意版本号。React 源码学习的 GitHub 仓库地址:https://github.com/wenzi0github/react。 在 lanes 模型中,我们可以通过各种的位运算,实现不同 lane 的组合,这里我们来了解下 lanes 模型中涉及到的几种。 位运算主要包含了按位与、按位或、异或、取反、左移、右移等操作。 在 lanes 模型,有很多对 1 的左移操作,如: 这段的作用是什么呢? 即可以得到 lanes 中 index 位置的 lane 模型,这个 lane 模型只有 index 位置是 1,方便后续在 lanes 中对 lane 进行位运算操作。 按位与 &,是同一个位置上都为 1 时,结果才会为 1。按位与的操作,通常是用来筛选出某些 lanes 模型的,即会收窄 lanes 模型中的数据。 A&B,我们做一个形象的比喻:A 中的 1 就像汽车,0 是没有车。B 就像车道,1 是畅通,0 是有阻挡。当执行 A&B 后得到的结果就是,通过 B 车道后,到达终点的车: 如: NonIdleLanes 表示未闲置任务的 lanes,pendingLanes & NonIdleLanes,即 pendingLanes 模型中,与 NonIdleLanes 模型重合的数据,都是 NonIdleLanes 范围内的,那就可以筛选出未闲置的任务。 还有,就是用来判断两个 lanes 的包含关系了,具体比较什么,还得看跟什么进行对比。 如下面的这段代码,NoLanes 的值为 0,若按位与操作后的结果不为 0,则说明 lanes 中至少了包含了一个 NonIdleLanes 类型的任务,即 lanes 和 NonIdleLanes 至少在同一个位置上都是是 1: 再如下面的这段代码,我们先不管 RetryLanes 的数值和含义: lanes 与它按位与操作后,再跟 lanes 本身进行判断,若还是相等,说明 lanes 要么为 NoLanes(0 与任何数按位与都是 0), 要么只包含了 RetryLanes 中几个或者全部的任务。举个栗子,若 RetryLanes 的值为 0b01100,那 lanes 对应的二进制中的 1,如果有的话,只能出现在 RetryLanes 中 1 的位置。提炼一下:lanes 中 1 的位置只能是 RetryLanes 的子集。 以上面比喻的车道来解释的话:RetryLanes 中有很多畅通的车道,lanes 中凡是有车的位置,一定是在畅通的位置上,一定可以走到终点;或者畅通的车道上没车也行。 但若按位与操作后的比较对象换了,就成另一个含义了。如这段代码: 函数 isSubsetOfLanes() 与上面的 函数 includesOnlyRetries() 区别在于,比较对象不一样。这里是按位与操作后,与后一个对象进行比较。若相等的话,则说明 set 完全包含了 subset,并且 set 其他位置可能还有 1。即 set 是 subset 的超集。若 subset 为 NoLanes 时,subset 是任何 set 的子集。 以上面比喻的车道来解释的话:subset 中畅通的车道上一定有车可以达到终点,不畅通的车道上也可以有车(但不一定);若 subset 为 NoLanes 时,即畅通的车道为 0,那 set 就可以为任意值。 按位或的操作,是将不同的优先级组合到一起。若组合之前就已经有这个优先级了,按位或操作时,也完全没有影响。 如: 在 root.pendingLanes 原有基础上,再合并其他的 lane 操作。 lanes 模型在多种位运算的组合下,呈现了更强大的威力。 涉及到的运算符是 按位与 和 取反 操作。如 我们来拆解这个操作。取反操作的优先级比按位与高,先执行取反操作,然后再按位与操作。对 suspendedLanes 取反操作后的赛道,还能通过的,必然都不是 suspendedLanes 里的任务了。 最高优先级的任务,就是二进制中最右边的那个 1。这里涉及到的运算符是 按位与 和 负号,操作上是 lanes 与自己的负数进行按位与操作: 我们刚才在注释里用了一个 5(0b101)的例子,再看一个例子。若 lanes 为 12(0b1100): 最后的结果为 0b0100,转为十进制的话就是 4。我们通过上面的函数计算下: React 源码有不少对 lanes 的循环操作,即操作 lanes 上每一个 1,如在函数 markRootSuspended() 中: 上面的循环 while 中的操作很少,就是将 lanes 模型每个 1 的位置的过期重置为初始值。 熟悉这些 lanes 的位运算后,后续通过对各种优先级的组合、筛选、过滤等操作,来调度任务时,就会熟悉很多。1. 位运算的基本使用 #
1.1 左移的操作 #
const lane = 1 << index; // index为lanes中某个1的位置,从右往左,从0开始数的位置
刚开始我想反了,以为是 index 左移 1 位。 其实是数字 1 左移 index 位,那么得到的是:const lane = 1 << index; // index为5,1<<5,1左移5位后得到的是 0b0100000
1.2 按位与的操作 #
const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
/**
* 判断lanes中是否有未闲置的任务
* @param lanes
* @returns {boolean}
*/
export function includesNonIdleWork(lanes: Lanes) {
return (lanes & NonIdleLanes) !== NoLanes;
}
/**
* 判断lanes中是否只包含重试任务
* @param lanes
* @returns {boolean}
*/
export function includesOnlyRetries(lanes: Lanes) {
return (lanes & RetryLanes) === lanes;
}
// 假设 RetryLanes = 0b01100
0b01000 & RetryLanes; // 0b01000, true
0b00100 & RetryLanes; // 0b00100, true
0b01100 & RetryLanes; // 0b01100, true
0b00000 & RetryLanes; // 0b00000, true
0b01010 & RetryLanes; // 0b01000, false
export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane) {
return (set & subset) === subset;
}
1.3 按位或的操作 #
root.pendingLanes |= updateLane; // 即 root.pendingLanes = root.pendingLanes | updateLane
2. 多种位运算的组合 #
2.1 排除某个 lanes #
// 从 nonIdlePendingLanes 中排除 suspendedLanes
const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
2.2 获取当前 lanes 中的最高优先级的任务 #
/**
* 获取lanes中最高优先级的那个数,即最右边的那个1
* 在位运算中,若有负数,则使用该负数的补码参与运算,
* 位运算的文档:https://www.cnblogs.com/CoutCodes/p/12557649.html
* 如lanes = 5 = 101 源码
* -lanes = -5 = 011 补码
* lanes & -lanes = 101 & 011 = 1
* 即最右边的1代表的那个数字
* @param lanes
* @returns {number}
*/
export function getHighestPriorityLane(lanes: Lanes): Lane {
return lanes & -lanes;
}
getHighestPriorityLane(12); // 4
getHighestPriorityLane(0b1100); // 4
2.3 对 lanes 的循环 #
let lanes = suspendedLanes;
while (lanes > 0) {
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
// 将该位置的过期时间重置为初始值,即-1
expirationTimes[index] = NoTimestamp;
lanes &= ~lane;
}
4. 总结 #
版权属于:
加速器之家
作品采用:
《
署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
》许可协议授权
评论