Concurrent模式下React的更新行为- 优先级模型

nero

发布于 2021-02-09 11:02:68

点击进入React源码调试仓库。

作为构建用户界面的JavaScript库,React以提升用户交互体验为核心,而实现这一目标较为重要的一点是优先响应用户交互触发的更新任务,其余不那么重要的任务要做出让步,我们把用户交互触发的任务称为高优先级任务,不那么重要的任务称为低优先级任务。

React的Concurrent模式下,不同优先级任务的存在会导致一种现象:高优先级的任务可以打断低优先级任务的执行。但是打断归打断,总归是要恢复低优先级任务的,于是,低优先级任务在打断之后被恢复。另外,倘若低优先级任务一直被高优先级任务打断,那么低优先级任务就会过期,会被强制执行掉。这就是我们要讨论的两个问题:高优先级任务插队饥饿问题

React对不同优先级任务的执行控制,它的目的只有一个,就是将最紧急的更新呈现给用户。接下来我会两篇文章来讨论这两个问题。但在开始之前, 我们需要先认识React任务优先级控制的基础:lane模型以及它与React更新的关系。

注意,本文中提到的React中的任务优先级与调度机制中Scheduler的任务优先级不是同一个概念,后者可由前者转化而来。

lane优先级模型

在React17中,优先级模型换成了新的lane模型。该模型将优先级分成了31个,对应31个二进制位。

const TotalLanes = 31;

export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;
export const SyncBatchedLane: Lane = /*                 */ 0b0000000000000000000000000000010;

export const InputDiscreteHydrationLane: Lane = /*      */ 0b0000000000000000000000000000100;
const InputDiscreteLanes: Lanes = /*                    */ 0b0000000000000000000000000011000;

const InputContinuousHydrationLane: Lane = /*           */ 0b0000000000000000000000000100000;
const InputContinuousLanes: Lanes = /*                  */ 0b0000000000000000000000011000000;

export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000100000000;
export const DefaultLanes: Lanes = /*                   */ 0b0000000000000000000111000000000;

const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000001000000000000;
const TransitionLanes: Lanes = /*                       */ 0b0000000001111111110000000000000;

const RetryLanes: Lanes = /*                            */ 0b0000011110000000000000000000000;

export const SomeRetryLane: Lanes = /*                  */ 0b0000010000000000000000000000000;

export const SelectiveHydrationLane: Lane = /*          */ 0b0000100000000000000000000000000;

const NonIdleLanes = /*                                 */ 0b0000111111111111111111111111111;

export const IdleHydrationLane: Lane = /*               */ 0b0001000000000000000000000000000;
const IdleLanes: Lanes = /*                             */ 0b0110000000000000000000000000000;

export const OffscreenLane: Lane = /*                   */ 0b1000000000000000000000000000000;

不同优先级级别的比较

从优先级的名字以及对应的位来看,越靠右优先级越高。每个任务进来之后,其持有的优先级标志:lane都会对应到某个位上去。

比如任务1持有的lane为:

0b0000000000000000000001000000000

任务2持有的lane为:~~~~

0b0000000000000000000000000001000

那么,任务2的优先级要高于任务1。

lane的计算原则

我们知道,每当产生一个更新,都随之会产生一个update对象,而这个update对象会有一个lane,表示这个更新它的优先级如何。这个lane是如何计算出来的呢?在相对固定的优先级范围中,优先指定级别较高的位,若该固定范围内的lanes位全部被指定,则移动到相邻的较低级别范围的lanes位中指定

举例来说:事件1触发了一个更新A,它的优先级范围是InputDiscreteLanes,

0b0000000000000000000000000011000

那么更新A的lane在计算时会优先去被指定为倒数第四个位,如果被占用,则指定为倒数第五个位,如果还是被占用,那么会下移到相邻的低级别优先级范围:InputContinuousLanes

0b0000000000000000000000011000000

在这个范围内去指定,如果该范围内也都被指定出去了,那么继续下移,重复这个过程,直到找到一个可用的位,指定给它,计算完成,放到update对象上,并且放到root.pendingLanes中,root.pendingLanes表示React需要在本次处理的那些更新。

如何判断是否应处理某个更新

一旦产生更新任务,React就会开始构建workInProgress树,在构建之前,会在root.pendingLanes中找到最紧急的lanes作为renderLanes,构建workInProgress树的workLoop带着这个renderLanes一路向下走,当处理到产生更新的fiber节点上update链表时,会依次判断update.lane是否在renderLanes中,若在,则进行处理,否则不处理。

export function processUpdateQueue<State>(
  workInProgress: Fiber,
  props: any,
  instance: any,
  renderLanes: Lanes,
): void {
  do {
    if (!isSubsetOfLanes(renderLanes, updateLane)) {
      // updateLane在renderLanes中,处理这个更新
    } else {
      // 否则不处理,跳过
    }
  } while (true);
}

因为lane的计算是向下指定的,所以如果某个lane不在renderLanes的范围,那一定是优先级不足,需要被跳过。

小结

在探讨React对于不同优先级的多任务共存的行为之前,我们简单了解了lane模型和更新之间的关系。但lane模型涉及大量的位运算,与优先级混淆在一起难以理解,为此,我对React中计算lane的重要文件:ReactFiberLane做了一些注释 ,可以为了解lane的工作机制提供一些帮助。

后面的两篇文章将会分别探讨高优先级任务插队和饥饿问题。

欢迎扫码关注公众号,发现更多技术文章