优先级类型
- React内部对于优先级的管理,贯穿运作流程的4个阶段(从输入到输出),根据其功能的不同,可以分为3种类型:
- 1 )fiber优先级(LanePriority)
- 位于 react-reconciler包,也就是Lane(车道模型)
- 2 )调度优先级(SchedulerPriority)
- 位于scheduler包
- 3 )优先级等级(ReactPriorityLevel)
- 位于react-reconciler包中的 SchedulerWithReactIntegration.js
- 负责上述2套优先级体系的转换.
- 1 )fiber优先级(LanePriority)
- Lane 是在 react@17.0.0的新特性.
Lane(车道模型)
-
英文单词lane翻译成中文表示"车道,航道"的意思,所以很多文章都将Lanes模型称为车道模型
-
Lane模型的源码在 ReactFiberLane.js,源码中大量使用了位运算
-
首先引入对Lane的解释, 这里简单概括如下:
- 1 )Lane类型被定义为二进制变量,利用了位掩码的特性,在频繁运算的时候占用内存少,计算速度快.
- Lane和Lanes就是单数和复数的关系,代表单个任务的定义为Lane,代表多个任务的定义为Lanes
- 2 )Lane是对于expirationTime的重构,以前使用expirationTime表示的字段,都改为了lane
renderExpirationTime -> renderlanes update.expirationTime -> update.lane fiber.expirationTime -> fiber.lanes fiber.childExpirationTime -> fiber.childLanes root.firstPendingTime and root.lastPendingTime -> fiber.pendingLanes
- 3 )使用Lanes模型相比expirationTime模型的优势
- Lanes把任务优先级从批量任务中分离出来
- 可以更方便的判断单个任务与批量任务的优先级是否重叠
// 判断:单task与batchTask的优先级是否重叠 // 1.通过expirationTime判断 const isTaskIncludedInBatch = priorityOfTask >= priorityOfBatch; // 2.通过Lanes判断 const isTaskIncludedInBatch =(task & batchOfTasks) !== 0; // 当同时处理一组任务,该组内有多个任务,且每个任务的优先级不一致 // 1. 如果通过expirationTime判断,需要维护一个范围(在Lane重构之前,源码中就是这样比较的) const isTaskIncludedInBatch = taskPriority <= highestPriorityInRange && taskPriority >= lowestPriorityInRange; // 2. 通过Lanes判断 const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;
- 1 )Lane类型被定义为二进制变量,利用了位掩码的特性,在频繁运算的时候占用内存少,计算速度快.
-
Lanes使用单个32位二进制变量即可代表多个不同的任务
-
也就是说一个变量即可代表一个组(group)
-
如果要在一个group中分离出单个task,非常容易
-
在expirationTime模型设计之初,react体系中还没有 Suspense 异步渲染的概念
-
现在有如下场景:有3个任务,其优先级A>B>C,正常来讲只需要按照优先级顺序执行就可以了
-
但是现在情况变了:A和C任务是CPU密集型,而B是IO密集型(Suspense会调用远程api,算是IO任务)
-
即A(cpu)>B(IO)>C(cpu).此时的需求需要将任务B从group中分离出来,先处理cpu任务A和C
//从group中删除或增加task // 通过expirationTime实现 // 维护一个链表,按照单个task的优先级顺序进行插入 // 删除单个task(从链表中删除一个元素) task. prev.next = task.next; //2)增加单个task(需要对比当前task的优先级,插入到链表正确的位置上) let current = queue; while (task.expirationTime >= current.expirationTime) { current = current.next; } task.next = current.next; current.next = task; //3)比较task是否在group中 const isTaskIncludedInBatch = taskPriority <= highestPriorityInRange && taskPriority >= lowestPriorityInRang; //2.通过Lanes实现 //1)删除单个task batchOfTasks &= ~task; //2)增加单个task batchOfTasks |= task; //3)比较task是否在group中 const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;
-
Lanes是一个不透明的类型,只能在ReactFiberLane.js这个模块中维护
-
如果要在其他文件中使用,只能通过 ReactFiberLane.js 中提供的工具函数来使用
-
分析车道模型的源码(ReactFiberLane.js中),可以得到如下结论:
- 1.可以使用的比特位一共有31位
- 2.共定义了18种车道(Lane/Lanes)变量,每一个变量占有1个或多个比特位,分别定义为Lane和Lanes类型.
- 3.每一种车道(Lane/Lanes)都有对应的优先级,所以源码中定义了18种优先级(LanePriority).
- 4.占有低位比特位的Lane变量对应的优先级越高
- 最高优先级为 SynclanePriority 对应的车道为
Synclane = 0b0000000000000000000000000000001
- 最低优先级为 OffscreenLanePriority 对应的车道为
OffscreenLane = 0b1000000000000000000000000000000
- 最高优先级为 SynclanePriority 对应的车道为
位运算
-
什么是位运算?程序中的所有数在计算机内存中都是以二进制的形式储存的。
-
位运算就是直接对整数在内存中的二进制位进行操作。
-
比如
- 0 在二进制中用 0 表示,我们用 0000 代表;
- 1 在二进制中用 1 表示,我们用 0001 代表;
-
那么先看两个位运算符号 & 和 |
- & 对于每一个比特位,两个操作数都为 1 时,结果为 1,否则为 0
- | 对于每一个比特位,两个操作数都为0时,结果为 0,否则为 1
- 我们看一下两个 1 & 0 和 1 | 0
- 如上 1 & 0 = 0, 1 | 0 = 1
-
参考
0 0000 1 0001 ------------- 0 & 1 = 0000 = 0
-
再参考
0 0000 1 0001 ---------------- 0 | 1 = 0001 = 1
使用一张表格详细说明
运算符 | 用法 | 描述 |
---|---|---|
与 & | a & b | 如果两位都是1则设置每位为1 |
或 | | a | b | 如果两位之一为1则设置每位为1 |
异或 ^ | a^b | 如果两位只有一位为1则设置每位为1 |
非 ~ | ~ a | 反转操作数的比特位, 即0变成1, 1变成0 |
左移(<<) | a << b | 将a的二进制形式向左移b(< 32)比特位, 右边用0填充 |
有符号右移(>>) | a>>b | 将a的二进制形式向右移b(<32)比特位,丢弃被移除的位,左侧以最高位来填充 |
无符号右移(>>>) | a>>>b | 将a的二进制形式向右移b(<32)比特位,丢弃被移除的位,并用0在左侧填充 |
位运算的一个使用场景
- 比如有一个场景下,会有很多状态常量A,B,C…,这些状态在整个应用中在一些关键节点中做流程控制
- 比如:
if(value === A) { // TODO.. }
- 如上判断value等于常量A,那么进入到if的条件语句中
- 此时是value属性是简单的一对一关系,但是实际场景下value可能是好几个枚举常量的集合
- 也就是一对多的关系,那么此时value可能同时代表A和B两个属性
- 如下图所示:
- 这时候,如果按照下面的代码来写,就会很麻烦
if(value === A || value === B) { // TODO.. }
- 此时的问题就是如何用一个value表示A和B两个属性的集合,这个时候位运算就派上用场了
- 因为可以把一些状态常量用32位的二进制来表示(这里也可以用其他进制),比如:
const A = 0b0000000000000000000000000000001 // 优先级最高 const B = 0b0000000000000000000000000000010 const C = 0b0000000000000000000000000000100 // 优先级最低
- 通过移位的方式让每一个常量都单独占一位,这样在判断一个属性是否包含常量的时候
- 可以根据当前位数的1和0来判断
- 这样如果一个值即代表A又代表B那么就可以通过位运算的 | 来处理
- 就有
AB = A | B = 0b0000000000000000000000000000011
- 那么如果把AB的值赋予给value,那么此时的value就可以用来代表A和B
- 此时当然不能直接通过等于或者恒等来判断value是否为A或者B,此时就可以通过&来判断。具体实现如下:
const A = 0b0000000000000000000000000000001 const B = 0b0000000000000000000000000000010 const C = 0b0000000000000000000000000000100 const N = 0b0000000000000000000000000000000 const value = A | B console.log((value & A ) !== N) // true console.log((value & B ) !== N) // true console.log((value & C ) !== N ) // false
位运算在 react 中的应用
export const NoLanes = /* */ 0b0000000000000000000000000000000;
const SyncLane = /* */ 0b0000000000000000000000000000001;
const InputContinuousHydrationLane = /* */ 0b0000000000000000000000000000010;
const InputContinuousLane = /* */ 0b0000000000000000000000000000100;
const DefaultHydrationLane = /* */ 0b0000000000000000000000000001000;
const DefaultLane = /* */ 0b0000000000000000000000000010000;
const TransitionHydrationLane = /* */ 0b0000000000000000000000000100000;
const TransitionLane = /* */ 0b0000000000000000000000001000000;
-
如上 SyncLane 代表的数值是1,它却是最高的优先级
-
也即是说lane的代表的数值越小,此次更新的优先级就越大
-
在新版本的React中,还有一个新特性,就是render阶段可能被中断,在这个期间会产生一个更高优先级的任务
-
那么会再次更新lane属性,这样多个更新就会合并,这样一个lane可能需要表现出多个更新优先级
-
我们来看一下React是如何通过位运算分离出优先级的
function getHighestPriorityLane(lanes) { return lanes & -lanes; }
-
如上就是通过 lanes & -lanes 分离出最高优先级的任务的,我们来看一下具体的流程
- 比如SyncLane和InputContinuousLane合并之后的任务优先级lane为
SyncLane = 0b0000000000000000000000000000001
InputContinuousLane = 0b0000000000000000000000000000100
lane = SyncLane|InputContinuousLane
lane = 0b0000000000000000000000000000101
- 那么通过 lanes & -lanes 分离出 SyncLane
- 首先我们看一下 -lanes,在二进制中需要用补码表示为:
-lane = 0b1111111111111111111111111111011
- 那么接下来执行 lanes & -lanes 看一下,& 的逻辑是如果两位都是1则设置改位为1,否则为0
- 那么 lane & -lane, 只有一位(最后一位)全是1,所有合并后的内容为:
lane & -lane = 0b0000000000000000000000000000001
-
可以看得出来lane&-lane的结果是SyncLane,所以通过lane&-lane就能分离出最高优先级的任务
const SyncLane = 0b0000000000000000000000000000001 const InputContinuousLane = 0b0000000000000000000000000000100 const lane = SyncLane | InputContinuousLane console.log((lane & -lane) === SyncLane ) // true