【React源码】(六)React 中的优先级管理

React 中的优先级管理

React内部对于优先级的管理, 根据功能的不同分为LanePrioritySchedulerPriorityReactPriorityLevel3 种类型. 本文基于react@17.0.2, 梳理源码中的优先级管理体系.

React是一个声明式, 高效且灵活的用于构建用户界面的 JavaScript 库. React 团队一直致力于实现高效渲染, 其中有 2 个十分有名的演讲:

  1. 2017 年 Lin Clark 的演讲中介绍了fiber架构和可中断渲染.
  2. 2018 年 Dan 在 JSConf 冰岛的演讲进一步介绍了时间切片(time slicing)和异步渲染(suspense)等特性.

演讲中所展示的可中断渲染,时间切片(time slicing),异步渲染(suspense)等特性, 在源码中得以实现都依赖于优先级管理.

React@17.0.2源码中, 一共有2套优先级体系1套转换体系, 在深入分析之前, 再次回顾一下(reconciler 运作流程):

React内部对于优先级的管理, 贯穿运作流程的 4 个阶段(从输入到输出), 根据其功能的不同, 可以分为 3 种类型:

  1. fiber优先级(LanePriority): 位于react-reconciler包, 也就是Lane(车道模型).
  2. 调度优先级(SchedulerPriority): 位于scheduler包.
  3. 优先级等级(ReactPriorityLevel) : 位于react-reconciler包中的SchedulerWithReactIntegration.js, 负责上述 2 套优先级体系的转换.

预备知识

在深入分析 3 种优先级之前, 为了深入理解LanePriority, 需要先了解Lane, 这是react@17.0.0的新特性.

Lane (车道模型)

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

Lane模型的源码在ReactFiberLane.js, 源码中大量使用了位运算(有关位运算的讲解, 可以参考React 算法之位运算).

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

  1. Lane类型被定义为二进制变量, 利用了位掩码的特性, 在频繁运算的时候占用内存少, 计算速度快.

    • LaneLanes就是单数和复数的关系, 代表单个任务的定义为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模型的优势:

    1. 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;

    2. 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的优越性, 运用起来代码量少, 简洁高效.

  4. Lanes是一个不透明的类型, 只能在ReactFiberLane.js这个模块中维护. 如果要在其他文件中使用, 只能通过ReactFiberLane.js中提供的工具函数来使用.

分析车道模型的源码(ReactFiberLane.js中), 可以得到如下结论:

  1. 可以使用的比特位一共有 31 位(为什么? 可以参考React 算法之位运算中的说明).
  2. 共定义了18 种车道(Lane/Lanes)变量, 每一个变量占有 1 个或多个比特位, 分别定义为LaneLanes类型.
  3. 每一种车道(Lane/Lanes)都有对应的优先级, 所以源码中定义了 18 种优先级(LanePriority).
  4. 占有低位比特位的Lane变量对应的优先级越高
    • 最高优先级为SyncLanePriority对应的车道为SyncLane = 0b0000000000000000000000000000001.
    • 最低优先级为OffscreenLanePriority对应的车道为OffscreenLane = 0b1000000000000000000000000000000.

优先级区别和联系

在源码中, 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中, 可以互转SchedulerPriority 和 ReactPriorityLevel:

​

// 把 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中, 可以互转LanePriority 和 ReactPriorityLevel:


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)等特性.

在理解了优先级的基本思路之后, 接下来就正式进入 react 源码分析中的硬核部分(scheduler 调度原理fiber树构造)

总结

本文介绍了 react 源码中有关优先级的部分, 并梳理了 3 种优先级之间的区别和联系. 它们贯穿了reconciler 运作流程中的 4 个阶段, 在 react 源码中所占用的代码量比较高, 理解它们的设计思路, 为接下来分析调度原理fiber构造打下基础.

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

优价实习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值