React中异步可中断更新是Concurrent模式最重要的一个特性。那如何保证任务中断之后的状态时正确的呢?
首先看这样一个组件:
function Counter() {
const [count, dispatch] = useState('start: ')
console.log('render text', count)
return (
<div>
<button
onClick={() => {
// 以不同的优先级调用
// 3: NormalPriority
// 1: ImmediatePriority
unsafe_setCurrentPriorityLevel(3)
dispatch((state) => state + 'A')
unsafe_setCurrentPriorityLevel(1)
dispatch((state) => state + 'B')
unsafe_setCurrentPriorityLevel(3)
dispatch((state) => state + 'C')
unsafe_setCurrentPriorityLevel(1)
dispatch((state) => state + 'D')
}}
>
123
</button>
<div>{count}</div>
</div>
)
}
这里 unsafe_setCurrentPriorityLevel 是可以修改调度优先级的函数,即可以手动修改 react-schduler包中的变量 currentPriorityLevel的。如果对调度工作原理不太熟,可以理解为 通过这个函数,dispatch这个动作就有对应的优先级,1表示高优先级,而3优先级较1低,表示普通优先级。
当执行完上述函数之后,Counter这个函数组件所对应的fiber的memoizedState中会存在一个如下图的循环链表。(源码中,会先在dispatchAction函数中形成单向循环列表挂在queue.pending,然后在updateReducer拷贝到baseQueue中,这里以状态变更为准,不去考虑这些。)

上图中每个方块表示一个Update,蓝色表示高优先级,透明表示低优先级。baseQueue指向最新的一条更新。
这里需要说明一点的是:在以不同优先级调用dispatch时,会将优先级记录(源码中pendingLanes),然后处理时先取最高优先级(源码中是用lanes去处理优先级,这里简化记为Priority_Flag,伪代码中会用到),所以这里会优先执行蓝色方块的Update。
通过baseQueue.next可以拿到链表的第一个Update,下面通过伪代码模拟针对每一个Update做的处理
newState = 'start: '
cloneUpdate = null
// FirstUpdate = A
// 开始: Update 指向 A
do {
// 如果`Update`优先级比
if (Update.Priority < Priority_Flag) {
// 克隆时保留优先级
newUpdate = cloneUpdate(Update)
// cloneUpdate也是单向循环链表,这里将 newUpdate 拼接上去
concatNewUpdate(newUpdate)
}
// 如果当前 Update 的优先级 大于等于 Hight_Priority,就执行当前 Update
newState = reduce(newState, action)
// 当存在优先级较低的 Update时,
// 也需要 clone 满足优先级的Update
// 但是这里有一个很重要的点:需要将 优先级 改成最高优先级(红色)
// 目的是 使用最高优先级之后,后续无论什么优先级,这个Update都会被使用
if (cloneUpdate !== null) {
newUpdate = cloneUpdate(Update)
}
// 拼接
concatNewUpdate(newUpdate)
Update = Update.next // 指向下一个 Update
} while(Update !== null || Update !== FirstUpdate)
通过上述代码之后,高优先级的Update即上述蓝色方块的Update会被使用:
Update-B 通过newState = reduce(newState, action)得到更新之后的结果为 newState = start: BUpdate-D 通过newState = reduce(newState, action)得到更新之后的结果为 newState = start: BD之后将cloneQueue再拷贝给BaseQueue

然后通过commit阶段,第一次打印的结果为 render text start: BD,因为含有未处理完的任务,所以会再次进入调度处理。
因为高优先级已被处理,这时的Priority_Flag为普通优先级。
如果再去执行一次上述的伪代码,那么所有的Update都会被执行,且cloneUpdate一直为null:
Update-A: 普通优先级,可以执行,结果为 start: AUpdate-B: 最高优先级,一定会执行执行,结果为 start: ABUpdate-C: 普通优先级,可以执行,结果为 start: ABCUpdate-D: 最高优先级,一定会执行执行,结果为 start: ABCD所以这一次的结果为 start: ABCD。
这里主要是利用链表维护了所有待更新的Update,在每一次调度中执行符合对应优先级的更新,同时保留所有未更新以及更新的Update的在链表中的先后关系,下一次重新从头计算状态,这样每次计算的状态都是符合要求的。