欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 建筑 > reactFiberLane

reactFiberLane

2024/10/25 9:28:52 来源:https://blog.csdn.net/liangzhenmeng/article/details/141200190  浏览:    关键词:reactFiberLane

Lane (车道模型)

英文单词lane翻译成中文表示"车道, 航道"的意思, 所以很多文章都将Lanes模型称为车道模型

Lane模型的源码在ReactFiberLane.js, 源码中大量使用了位运算(有关位运算的讲解,

首先引入作者对Lane的解释(相应的 pr), 这里简单概括如下:

  1. Lane类型被定义为二进制变量, 利用了位掩码的特性, 在频繁运算的时候占用内存少, 计算速度快.
    1. LaneLanes就是单数和复数的关系, 代表单个任务的定义为Lane, 代表多个任务的定义为Lanes
  2. Lane是对于expirationTime的重构, 以前使用expirationTime表示的字段, 都改为了lane
  renderExpirationtime -> renderLanesupdate.expirationTime -> update.lanefiber.expirationTime -> fiber.lanesfiber.childExpirationTime -> fiber.childLanesroot.firstPendingTime and root.lastPendingTime -> fiber.pendingLanes
  1. 使用Lanes模型相比expirationTime模型的优势:
  2. 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;

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//1. 通过expirationTime实现
// 0) 维护一个链表, 按照单个task的优先级顺序进行插入
// 1) 删除单个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 >= lowestPriorityInRange;// 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 个或多个比特位, 分别定义为LaneLanes类型.
  3. 每一种车道(Lane/Lanes)都有对应的优先级, 所以源码中定义了 18 种优先级(LanePriority).
  4. 占有低位比特位的Lane变量对应的优先级越高
    1. 最高优先级为SyncLanePriority对应的车道为SyncLane = 0b0000000000000000000000000000001.
    2. 最低优先级为OffscreenLanePriority对应的车道为OffscreenLane = 0b1000000000000000000000000000000.

位运算

什么是位运算? 程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算就是直接对整数在内存中的二进制位进行操作。

比如

  • 0 在二进制中用 0 表示,我们用 0000 代表;
  • 1 在二进制中用 1 表示,我们用 0001 代表;

那么先看两个位元算符号 & 和 |:

  • & 对于每一个比特位,两个操作数都为 1 时, 结果为 1, 否则为 0
  • | 对于每一个比特位,两个操作数都为 0 时, 结果为 0, 否则为 1

我们看一下两个 1 & 0 和 1 | 0

如上 1 & 0 = 0 ,1 | 0 = 1

在这里插入图片描述
在这里插入图片描述

位运算的一个使用场景:

比如有一个场景下,会有很多状态常量 A,B,C…,这些状态在整个应用中在一些关键节点中做流程控制,比如:

if(value === A){ // TODO… }

如上判断 value 等于常量A ,那么进入到 if 的条件语句中。 此时是 value 属性是简单的一对一关系,但是实际场景下 value 可能是好几个枚举常量的集合,也就是一对多的关系,那么此时 value 可能同时代表 A 和 B 两个属性。如下图所示:

在这里插入图片描述

此时的问题就是如何用一个 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

优先级区别和联系

在源码中, 3 种优先级位于不同的 js 文件, 是相互独立的.

注意:

  • LanePrioritySchedulerPriority从命名上看, 它们代表的是优先级
  • ReactPriorityLevel从命名上看, 它代表的是等级而不是优先级, 它用于衡量LanePrioritySchedulerPriority的等级.

LanePriority

LanePriority`: 属于`react-reconciler`包, 定义于`ReactFiberLane.js
export const SyncLanePriority: LanePriority = 15;
export const SyncBatchedLanePriority: LanePriority = 14;const InputDiscreteHydrationLanePriority: LanePriority = 13;
export const InputDiscreteLanePriority: LanePriority = 12;// .....const OffscreenLanePriority: LanePriority = 1;
export const NoLanePriority: LanePriority = 0;

fiber构造过程相关的优先级(如fiber.updateQueue,fiber.lanes)都使用LanePriority.

由于本节重点介绍优先级体系以及它们的转换关系, 关于Lane(车道模型)fiber树构造时的具体使用, 在fiber 树构造章节详细解读.

SchedulerPriority

SchedulerPriority, 属于scheduler包, 定义于SchedulerPriorities.js

export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;

scheduler调度中心相关的优先级使用SchedulerPriority.

ReactPriorityLevel

reactPriorityLevel, 属于react-reconciler包, 定义于SchedulerWithReactIntegration.js

export const ImmediatePriority: ReactPriorityLevel = 99;
export const UserBlockingPriority: ReactPriorityLevel = 98;
export const NormalPriority: ReactPriorityLevel = 97;
export const LowPriority: ReactPriorityLevel = 96;
export const IdlePriority: ReactPriorityLevel = 95;
// NoPriority is the absence of priority. Also React-only.
export const NoPriority: ReactPriorityLevel = 90;

LanePrioritySchedulerPriority通过ReactPriorityLevel进行转换

转换关系

为了能协同调度中心(scheduler包)和 fiber 树构造(react-reconciler包)中对优先级的使用, 则需要转换SchedulerPriorityLanePriority, 转换的桥梁正是ReactPriorityLevel.

SchedulerWithReactIntegration.js中, 可以互转SchedulerPriorityReactPriorityLevel:

// 把 SchedulerPriority 转换成 ReactPriorityLevel
export function getCurrentPriorityLevel(): ReactPriorityLevel {switch (Scheduler_getCurrentPriorityLevel()) {case Scheduler_ImmediatePriority:return ImmediatePriority;case Scheduler_UserBlockingPriority:return UserBlockingPriority;case Scheduler_NormalPriority:return NormalPriority;case Scheduler_LowPriority:return LowPriority;case Scheduler_IdlePriority:return IdlePriority;default:invariant(false, 'Unknown priority level.');}
}// 把 ReactPriorityLevel 转换成 SchedulerPriority
function reactPriorityToSchedulerPriority(reactPriorityLevel) {switch (reactPriorityLevel) {case ImmediatePriority:return Scheduler_ImmediatePriority;case UserBlockingPriority:return Scheduler_UserBlockingPriority;case NormalPriority:return Scheduler_NormalPriority;case LowPriority:return Scheduler_LowPriority;case IdlePriority:return Scheduler_IdlePriority;default:invariant(false, 'Unknown priority level.');}
}

ReactFiberLane.js中, 可以互转LanePriorityReactPriorityLevel:

export function schedulerPriorityToLanePriority(schedulerPriorityLevel: ReactPriorityLevel,
): LanePriority {switch (schedulerPriorityLevel) {case ImmediateSchedulerPriority:return SyncLanePriority;// ... 省略部分代码default:return NoLanePriority;}
}export function lanePriorityToSchedulerPriority(lanePriority: LanePriority,
): ReactPriorityLevel {switch (lanePriority) {case SyncLanePriority:case SyncBatchedLanePriority:return ImmediateSchedulerPriority;// ... 省略部分代码default:invariant(false,'Invalid update priority: %s. This is a bug in React.',lanePriority,);}
}

优先级使用

通过reconciler运行流程中的归纳, reconciler从输入到输出一共经历了 4 个阶段, 在每个阶段中都会涉及到与优先级相关的处理. 正是通过优先级的灵活运用, React实现了可中断渲染,时间切片(time slicing),异步渲染(suspense)等特性.

突然很后悔当初没有跟老师好好学数电,在此缅怀许院王教授

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com