说到高优先级任务插队机制,就要提到Reat fiber这个东西了,也就是时间分片,说实话这东西我之前了解过,但是下午被面试官问到了,我还说都能答上来,结果突然问到时间分片后,被分化的小任务的执行顺序,还有就是如果有优先级高的任务插入进来,它们的执行顺序是什么,当时头皮发麻,想着我都说到这里了,怎么还问,刚刚看了下,其实原理很简单,下面一起来看看吧!
在React的concurrent模式下,低优先级任务执行过程中,一旦有更高优先级的任务进来,那么这个低优先级的任务会被取消,优先执行高优先级任务。等高优先级任务做完了,低优先级任务会被重新做一遍。
我们用一个具体的例子来理解一下高优先级任务插队。
有这样一个组件,state为0,进入页面,会调用setState将state加1,这个作为低优先级任务。React开始进行更新,在这个低优先级任务尚未完成时,模拟按钮点击,state加2,这个作为高优先级任务。可以看到,页面上的数字变化为0 -> 2 -> 3,而不是0 -> 1 -> 3。这就说明,当低优先级任务(加1)正在进行时,高优先级任务进来了,而它会把state设置为2。由于高优先级任务的插队,设置state为1的低优先级任务会被取消,先做高优先级任务,所以数字从0变成了2。而高优先级任务完成之后,低优先级任务会被重做,所以state再从2加到了3。
现象如下:
利用chrome的性能分析工具捕捉更新过程,可以明显看到优先级插队的过程
完整的profile文件我保存下来了,可以载入到chrome中详细查看:高优先级插队.json 。
可以再看一下这个过程中两个任务优先级在调度过程中的信息
点击查看 高优先级插队示例代码文件。
点击查看 低优先级任务饥饿问题示例代码文件。
接下来我们就来从setState开始,探讨一下这种插队行为的本质,内容涉及update对象的生成、发起调度、工作循环、高优任务插队、update对象的处理、低优先级任务重做等内容。
产生更新
当调用setState时,意味着组件对应的fiber节点产生了一个更新。setState实际上是生成一个update对象,调用enqueueSetState,将这个update对象连接到fiber节点的updateQueue链表中.
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
复制代码
enqueueSetState的职责是创建update对象,将它入队fiber节点的update链表(updateQueue),然后发起调度。
enqueueSetState(inst, payload, callback) {
// 获取当前触发更新的fiber节点。inst是组件实例
const fiber = getInstance(inst);
// eventTime是当前触发更新的时间戳
const eventTime = requestEventTime();
const suspenseConfig = requestCurrentSuspenseConfig();
// 获取本次update的优先级
const lane = requestUpdateLane(fiber, suspenseConfig);
// 创建update对象
const update = createUpdate(eventTime, lane, suspenseConfig);
// payload就是setState的参数,回调函数或者是对象的形式。
// 处理更新时参与计算新状态的过程
update.payload = payload;
// 将update放入fiber的updateQueue
enqueueUpdate(fiber, update);
// 开始进行调度
scheduleUpdateOnFiber(fiber, lane, eventTime);
}
复制代码
梳理一下enqueueSetState中具体做的事情:
找到fiber
首先获取产生更新的组件所对应的fiber节点,因为产生的update对象需要放到fiber节点的updateQueue上。然后获取当前这个update产生的时间,这与更新的饥饿问题相关,我们暂且不考虑,而且下一步的suspenseConfig可以先忽略。
计算优先级
之后比较重要的是计算当前这个更新它的优先级lane:
const lane = requestUpdateLane(fiber, suspenseConfig);
复制代码
计算这个优先级的时候,是如何决定根据什么东西去计算呢?这还得从React的合成事件说起。
事件触发时,合成事件机制调用scheduler中的runWithPriority函数,目的是以该交互事件对应的事件优先级去派发真正的事件流程。runWithPriority会将事件优先级转化为scheduler内部的优先级并记录下来。当调用requestUpdateLane计算lane的时候,会去获取scheduler中的优先级,以此作为lane计算的依据。
这部分的源码在这里
创建update对象, 入队updateQueue
根据lane和eventTime还有suspenseConfig,去创建一个update对象,结构如下:
const update: Update<*> = {
eventTime,
lane,
suspenseConfig,
tag: UpdateState,
payload: n