更新优先级

React中,更新分为两种,紧急更新和过渡更新:

  • 紧急更新(Urgent updates):用户交互等,比如点击输入按键等等,由于直接影响到用户的使用体验,属于紧急情况。
  • 过渡更新(Transition updates):如从一个界面过渡到另一个界面,属于非紧急情况。

对于用户体验来讲,紧急更新应该是优先于非紧急更新的。例如用input搜索时,我们应该确保用户输入的内容是能够是实时响应的,而根据输入值搜索出来的内容在渲染更新的时候不应该阻塞用户的输入。

这里就回到了上面提到的多更新并存的问题:哪些更新优先级高,哪些更新优先级低,哪些更新需要立即去执行,哪些更新可以缓一缓再执行。

为了解决这个问题,React为通过lane的方式每个更新分配了相关优先级。lane可以简单理解为一些数字,数值越小,表明优先级越高。但是为了计算方便,采用二进制的形式来表示。比如我们在判断一个状态的更新是否属于当前更新时,只需要判断updateLanes & renderLanes即可。

react-reconciler/src/ReactFiberLane.new.js 文件中,里面一共展示了32lane

export const TotalLanes = 31;

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

// 同步
export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;

// 连续事件
export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lanes = /*            */ 0b0000000000000000000000000000100;

// 默认
export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000000001000;
export const DefaultLane: Lanes = /*                    */ 0b0000000000000000000000000010000;

// 过渡
const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000000000000100000;
const TransitionLanes: Lanes = /*                       */ 0b0000000001111111111111111000000;
const TransitionLane1: Lane = /*                        */ 0b0000000000000000000000001000000;
const TransitionLane2: Lane = /*                        */ 0b0000000000000000000000010000000;
const TransitionLane3: Lane = /*                        */ 0b0000000000000000000000100000000;
const TransitionLane4: Lane = /*                        */ 0b0000000000000000000001000000000;
const TransitionLane5: Lane = /*                        */ 0b0000000000000000000010000000000;
const TransitionLane6: Lane = /*                        */ 0b0000000000000000000100000000000;
const TransitionLane7: Lane = /*                        */ 0b0000000000000000001000000000000;
const TransitionLane8: Lane = /*                        */ 0b0000000000000000010000000000000;
const TransitionLane9: Lane = /*                        */ 0b0000000000000000100000000000000;
const TransitionLane10: Lane = /*                       */ 0b0000000000000001000000000000000;
const TransitionLane11: Lane = /*                       */ 0b0000000000000010000000000000000;
const TransitionLane12: Lane = /*                       */ 0b0000000000000100000000000000000;
const TransitionLane13: Lane = /*                       */ 0b0000000000001000000000000000000;
const TransitionLane14: Lane = /*                       */ 0b0000000000010000000000000000000;
const TransitionLane15: Lane = /*                       */ 0b0000000000100000000000000000000;
const TransitionLane16: Lane = /*                       */ 0b0000000001000000000000000000000;

// 重试
const RetryLanes: Lanes = /*                            */ 0b0000111110000000000000000000000;
const RetryLane1: Lane = /*                             */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /*                             */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /*                             */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /*                             */ 0b0000010000000000000000000000000;
const RetryLane5: Lane = /*                             */ 0b0000100000000000000000000000000;

export const SomeRetryLane: Lane = RetryLane1;

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

const NonIdleLanes = /*                                 */ 0b0001111111111111111111111111111;

export const IdleHydrationLane: Lane = /*               */ 0b0010000000000000000000000000000;
export const IdleLane: Lanes = /*                       */ 0b0100000000000000000000000000000;

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

不同的lane表示不同的更新优先级。比如用户事件比较紧急,那么可以对应比较高的优先级如SyncLaneUI界面过渡的更新不那么紧急,可以对应比较低的优先级如TransitionLane;网络加载的更新也不那么紧急,可以对应低优先级RetryLane,等等。

通过这种优先级,我们就能判断哪些更新优先执行,哪些更新会被中断滞后执行了。举个例子来讲:假如有两个更新,他们同时对App组件的一个count属性更新:

<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
  A按钮
</button>
<button onClick={() => startTransition(() => { setCount(count + 1) })}>
  B按钮
</button>
  • 一个是A按钮:click事件触发的更新,叫做A更新,对应于SyncLane
  • 一个是B按钮:startTransition触发的更新,叫做B更新,对应于TransitionLane1

假设B按钮先点击, B更新开始,按照之前提到时间切片的形式进行更新。中途触发了A按钮点击,进而触发A更新。那么此时就会通过lane进行对比,发现DefaultLane优先级高于TransitionLane1。此时会中断B更新,开始A更新。直到A更新完成时,再重新开始B更新。
image.png

那么React是如何区分B更新Appcount的更改和A更新中对count的更改呢?

实际上,在每次更新时,更新 state的操作会被创建为一个 Update,放到循环链表当中:

export function createUpdate(eventTime: number, lane: Lane): Update<*> {
  const update: Update<*> = {
    eventTime,
    lane,

    tag: UpdateState,
    payload: null,
    callback: null,

    next: null,
  };
  return update;
}

在更新的时候就会依次去执行这个链表上的操作,从而计算出最终的state

Update的定义可以注意到,每个Update里都有一个lane属性。该属性标识了当前的这个Update的更新优先级,属于哪个更新任务中的操作。

因此当A更新在执行的时候,我们在计算state的时候,只需要去计算与A更新相同laneupdate即可。同样,B更新开始,也只更新具有同等lane级别的Update,从而达到不同更新的状态互不干扰的效果。

React18 并发渲染

回顾一下前面讨论的React并发渲染

  1. 为什么需要并发?
    1. 因为我们期望一些不重要的更新不会影响用户的操作,比如长列表渲染不会阻塞用户input输入,从而提升用户体验。
  2. 并发模式是怎样的?
    1. 在多个更新并存的情况下,我们需要根据更新优先级,优先执行紧急的更新,其次再执行不那么紧急的更新。比如优先响应click事件触发的更新,其次再响应长列表渲染的更新。
  3. 并发模式是如何实现的?
    1. 对于每个更新,为其分配一个优先级lane,用于区分其紧急程度。
    2. 通过Fiber结构将不紧急的更新拆分成多段更新,并通过宏任务的方式将其合理分配到浏览器的帧当中。这样就能使得紧急任务能够插入进来。
    3. 高优先级的更新会打断低优先级的更新,等高优先级更新完成后,再开始低优先级更新。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部