更新优先级
在React
中,更新分为两种,紧急更新和过渡更新:
- 紧急更新(
Urgent updates
):用户交互等,比如点击
,输入
,按键
等等,由于直接影响到用户的使用体验,属于紧急情况。 - 过渡更新(
Transition updates
):如从一个界面过渡到另一个界面,属于非紧急情况。
对于用户体验来讲,紧急更新应该是优先于非紧急更新的。例如用input
搜索时,我们应该确保用户输入的内容是能够是实时响应的,而根据输入值搜索出来的内容在渲染更新的时候不应该阻塞用户的输入。
这里就回到了上面提到的多更新并存的问题:哪些更新优先级高,哪些更新优先级低,哪些更新需要立即去执行,哪些更新可以缓一缓再执行。
为了解决这个问题,React
为通过lane
的方式每个更新分配了相关优先级。lane
可以简单理解为一些数字,数值越小,表明优先级越高。但是为了计算方便,采用二进制的形式来表示。比如我们在判断一个状态的更新是否属于当前更新时,只需要判断updateLanes & renderLanes
即可。
在react-reconciler/src/ReactFiberLane.new.js
文件中,里面一共展示了32
条lane
:
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
表示不同的更新优先级。比如用户事件比较紧急,那么可以对应比较高的优先级如SyncLane
;UI
界面过渡的更新不那么紧急,可以对应比较低的优先级如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
更新。
那么React
是如何区分B更新
对App
的count
的更改和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更新
相同lane
的update
即可。同样,B更新
开始,也只更新具有同等lane
级别的Update
,从而达到不同更新的状态互不干扰的效果。
React18 并发渲染
回顾一下前面讨论的React并发渲染
:
- 为什么需要并发?
- 因为我们期望一些不重要的更新不会影响用户的操作,比如长列表渲染不会阻塞用户
input
输入,从而提升用户体验。
- 因为我们期望一些不重要的更新不会影响用户的操作,比如长列表渲染不会阻塞用户
- 并发模式是怎样的?
- 在多个更新并存的情况下,我们需要根据更新优先级,优先执行紧急的更新,其次再执行不那么紧急的更新。比如优先响应
click
事件触发的更新,其次再响应长列表渲染的更新。
- 在多个更新并存的情况下,我们需要根据更新优先级,优先执行紧急的更新,其次再执行不那么紧急的更新。比如优先响应
- 并发模式是如何实现的?
- 对于每个更新,为其分配一个优先级
lane
,用于区分其紧急程度。 - 通过
Fiber
结构将不紧急的更新拆分成多段更新,并通过宏任务的方式将其合理分配到浏览器的帧当中。这样就能使得紧急任务能够插入进来。 - 高优先级的更新会打断低优先级的更新,等高优先级更新完成后,再开始低优先级更新。
- 对于每个更新,为其分配一个优先级