requestWork
1 )概述
- 在 scheduleWork 中,找到了创建更新的fiber对应的root节点
- 然后对它进行了一些操作之后,调用了
requestWork
,开始请求工作 - 在 requestWork 里面它会做哪些东西呢?
- 首先我们要把这个root节点加入到调度队列当中
- 然后判断是否是批量更新
- 最后再根据 expirationTime 的类型来判断调度的类型
2 )源码
react-reconciler/src/ReactFiberScheduler.js
找到 requestWork
js
// requestWork is called by the scheduler whenever a root receives an update.
// It's up to the renderer to call renderRoot at some point in the future.
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
addRootToSchedule(root, expirationTime);
if (isRendering) {
// Prevent reentrancy. Remaining work will be scheduled at the end of
// the currently rendering batch.
return;
}
if (isBatchingUpdates) {
// Flush work at the end of the batch.
if (isUnbatchingUpdates) {
// ...unless we're inside unbatchedUpdates, in which case we should
// flush it now.
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
performWorkOnRoot(root, Sync, true);
}
return;
}
// TODO: Get rid of Sync and use current time?
if (expirationTime === Sync) {
performSyncWork();
} else {
scheduleCallbackWithExpirationTime(root, expirationTime);
}
}
-
这里,它接受的参数 一个是 root,另外一个是 expirationTime
- 这个 expirationTime 并不是我们在创建更新的时候的 expirationTime
- 它是经过一系列处理之后得到的 expirationTime,它是用来记录任务的过期时间的
-
进来之后的第一个方法是
addRootToSchedule
jsfunction addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) { // Add the root to the schedule. // Check if this root is already part of the schedule. if (root.nextScheduledRoot === null) { // This root is not already scheduled. Add it. root.expirationTime = expirationTime; if (lastScheduledRoot === null) { firstScheduledRoot = lastScheduledRoot = root; root.nextScheduledRoot = root; } else { lastScheduledRoot.nextScheduledRoot = root; lastScheduledRoot = root; lastScheduledRoot.nextScheduledRoot = firstScheduledRoot; } } else { // This root is already scheduled, but its priority may have increased. const remainingExpirationTime = root.expirationTime; if ( remainingExpirationTime === NoWork || expirationTime < remainingExpirationTime ) { // Update the priority. root.expirationTime = expirationTime; } } }
- 判断
if (root.nextScheduledRoot === null)
, 符合则进行更新- 说明这个 root 之前没有进入过调度
- 内部如果 lastScheduledRoot 为空
- 注意 firstScheduledRoot 和 lastScheduledRoot 是单项链表结构
- 用于存储react中的所有 root 的调度关系
- 如果存在多个root, 可能会形成一个链表的结构
- 对于只有一个root的情况,只会存在
root.nextScheduledRoot = root
即,等于自己这种情况 - 这种情况会出现,是因为,先有了一个异步调度的任务
- 因为一次一个时间片内执行不完,执行不完,把JS的执行权交给浏览器
- 浏览器这个时候触发了 react 当中的一个新的更新
- 然后这个更新进来的时候,在react里面,它总共有两种不同优先级的更新
- 这个时候就会调用两次
addRootToSchedule
- 这个时候
root.nextScheduledRoot = root
就等于它自己 - 当然也会存在着有多个root的情况,这个时候就会形成一个链条
- 这边就是通过判断是否存在 lastScheduledRoot 来判断现在是否有任务正在进行调度
- 如果没有的话,我们就把first和last直接赋值成root就可以了
- 那如果有的话,更新 lastScheduleddRoot及其nextSchedulRoot
- 这就是一个把当前的这个root插到这个调度队列的最后一个的操作
- 即单项链表插入到最后一个的操作
- 如果已经进入过调度
- 获取当前的 expirationTime 并进行判断
- 如果当前的 expirationTime 是 NoWork 或 新的调度的 expirationTime 比当前任务优先级大
- 则更新 root.expirationTime 成最高优先级
- 因为 expirationTime 必须是 root 上优先级最高的
- 我们要先执行优先级高的任务
- 对于 root 来说,如果是一个异步调度,expirationTime 最小的任务,过期时间最少
- 进入到后期调度的过程中,按照比它优先级低的来做,等到它的过期时间到了
- 我们的调度进程中的判断标准还没有认为过期,就不会强制执行,从而导致任务被推迟
- 所以,必须把 root.expirationTime 设置成优先级最高的
- 判断
-
接下来,判断是否在渲染中,
if (isRendering)
- 说明调度在执行了,这里直接 return
- react 的调度是有个循环的,会循环 firstScheduledRoot 和 lastScheduledRoot 上面的任务
- 最终把所有任务更新完,之后把 firstScheduledRoot 和 lastScheduledRoot 清空成 null
- isRendering 说明循环已经开始,直接return就行
-
接下来进入批处理的判断,
if (isBatchingUpdates)
- isBatchingUpdates 和 isUnbatchingUpdates 是两个全局变量
- 是批处理相关的,先跳过
-
最后,通过 expirationTime是否为 Sync的判断
- 如果是 Sync,则调用
performSyncWork
方法- 相当于同步调用 js 代码,一直执行到结束为止
- 无法被打断,如果层级很深,则会占用js较长时间,可能导致应用卡顿
- 在以前版本没有 async 的情况,都是执行 performSyncWork
- 这样,它的瓶颈是很大的,性能不好
- 在 react16之后,保留了 sync 的模式,因为应用可能需要这种场景
- 如果不是,则调用
scheduleCallbackWithExpirationTime
方法- 这里进行异步的调度
- 这里进入了 react 中的独立的包 scheduler
- 这个包帮我们使用类似 requestIdleCallback 的方式帮助我们在浏览器有空闲的情况下
- 去执行不是很重要的任务,并且给我们设置 deadline
- 在 deadline 之前可以执行,在这个 deadline 之后,必须先把执行权交还给浏览器
- 再等它有空,再来执行任务
- 这个包用于提升前端应用开发的效率
- 如果是 Sync,则调用