resetChildExpirationTime
1 )概述
- 在
completeUnitOfWork
当中,有一步比较重要的一个操作,就是重置childExpirationTime
- childExpirationTime 是非常重要的一个时间节点,它用来记录某一个节点的子树当中,目前优先级最高的那个更新
- 整个应用的调度过程当中使用的都是root节点,在 scheduleWork 的时候,即便我们创建更新的那个节点,是我们写的某一个组件
- 但最终要先找到那一个root节点,然后再把它放到调度队列当中,因为会有这样的一个情况的存在
- 所以我们对于一个 reactApp 来说,它某一个节点下面可能是会存在非常多的一个子树的
- 每棵子树它创建的不同的任务,它的 expirationTime 都会不一样的
- 通过 childExpirationTime 来集中,最终可以在 root 上面能够快速的找到整个应用当中优先级最高的那个任务
- 举个例子,假设同时在 Input 和 List 两个组件内都去创建了一个异步的更新,创建这个异步的更新的过程当中
- 假设,Input的 ExpirationTime 优先级比较高,List 的 expirationTime 优先级比较少
- 对于div节点来说,它记录的是Input节点的 expirationTime, 因为它的优先级比较高
- 对于div来说,它如果下一个任务要去更新,它的优先级肯定是先更新 Input,而不会先更新 List
- 对于 RootFiber 和 App来说,因为它们的child只有div, 所以它们记录的会是 div 上面指定的那个优先级最高的 expirationTime
- 所以说
childExpirationTime
对于有分叉的点来说是非常重要的, 它可以记录不同的子树所创建的不同的更新 - 如果我们的分叉变得越来越多,有非常多个的时候,那么用这种方式来记录它的效率明显是会是更高一点的
- 对于这个情况下,如果我 Input 这个更新已经执行完了,它上面已经没有 expirationTime,因为它没有任务要去更新了
- 对于div来说,这个时候如果它还是认为这个Input它之前的 expirationTime 是最高优先级的话,那就不对了,之后的更新可能就会有出现问题
- 所以对于div这个节点,我们执行了
completeUnitOfWork
之后,要去更新它的childExpirationTime
- 对于每一个节点都是一样的, 在上述这个例子里面,特别重要的一点就是 div, div 要更新,那么它上面的节点都是要更新的
- 因为它们之前记录的都是 div 记录的那个值,所以,它们执行
completeUnitOfWork
的时候,也都要去做相同的操作
2 )源码
定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L989
对应到代码里面,来看在 completeUnitOfWork
里面,我们这边执行完了 completeWork
马上会执行 resetChildExpirationTime
那么这个方法它具体做了什么呢?
function resetChildExpirationTime(
workInProgress: Fiber,
renderTime: ExpirationTime,
) {
// 首先判断 renderTime ,就是当前正在执行更新的那一个优先级对应的 expirationTime
// 如果它不等于never,并且 workInProgress.childExpirationTime 等于 Never
// 也就是说 workInProgress 就是我们当前节点的子节点优先级最高的那个任务是never
// 就是永远不会更新到的并且我们现在也不是正在执行,never,就是永远不会更新到的那些节点的更新
// 说明我们这边根本就不需要做任何的操作,所以我们直接return就可以了
if (renderTime !== Never && workInProgress.childExpirationTime === Never) {
// The children of this component are hidden. Don't bubble their
// expiration times.
return;
}
let newChildExpirationTime = NoWork;
// Bubble up the earliest expiration time.
// 跳过这个if, 直接到 else
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
// We're in profiling mode.
// Let's use this same traversal to update the render durations.
let actualDuration = workInProgress.actualDuration;
let treeBaseDuration = workInProgress.selfBaseDuration;
// When a fiber is cloned, its actualDuration is reset to 0.
// This value will only be updated if work is done on the fiber (i.e. it doesn't bailout).
// When work is done, it should bubble to the parent's actualDuration.
// If the fiber has not been cloned though, (meaning no work was done),
// Then this value will reflect the amount of time spent working on a previous render.
// In that case it should not bubble.
// We determine whether it was cloned by comparing the child pointer.
const shouldBubbleActualDurations =
workInProgress.alternate === null ||
workInProgress.child !== workInProgress.alternate.child;
let child = workInProgress.child;
while (child !== null) {
const childUpdateExpirationTime = child.expirationTime;
const childChildExpirationTime = child.childExpirationTime;
if (childUpdateExpirationTime > newChildExpirationTime) {
newChildExpirationTime = childUpdateExpirationTime;
}
if (childChildExpirationTime > newChildExpirationTime) {
newChildExpirationTime = childChildExpirationTime;
}
if (shouldBubbleActualDurations) {
actualDuration += child.actualDuration;
}
treeBaseDuration += child.treeBaseDuration;
child = child.sibling;
}
workInProgress.actualDuration = actualDuration;
workInProgress.treeBaseDuration = treeBaseDuration;
} else {
// 这边主要是有一个 while循环, 这个while循环它首先使用的是 workInProgress.child
// 它就是我们当前节点的child,然后获取child的 expirationTime 以及 childChildExpirationTime
// child.expirationTime 是它自身它所创建的更新对应的 expirationTime
// 而 child.childExpirationTime 是child的子树里面任何几个节点它所创建的更新所对应的优先级最高的 expirationTime
let child = workInProgress.child;
while (child !== null) {
// 为什么这边要用这两个值?因为我们没有办法直接拿到当前节点它所有子树最高优先级的点
// 只能是通过去遍历它的所有的第一层的子节点以及每个子节点它的 childExpirationTime
// 因为我们这个 completeUnitOfWork 是由底往上去更新的一个过程
// 那么由底往上更新的过程,就会每一个节点都会对应这个操作
// 对应的这个操作之后,它肯定就是每一个节点都会更新到优先级最高的那个 expirationTime
const childUpdateExpirationTime = child.expirationTime;
const childChildExpirationTime = child.childExpirationTime;
// 接下来进行判断更新
if (childUpdateExpirationTime > newChildExpirationTime) {
newChildExpirationTime = childUpdateExpirationTime;
}
if (childChildExpirationTime > newChildExpirationTime) {
newChildExpirationTime = childChildExpirationTime;
}
// 下一个节点
child = child.sibling;
}
}
workInProgress.childExpirationTime = newChildExpirationTime;
}
- 以上注释写在代码里,简单来说
- 就是在每一次 complete 一个节点之后就要
- 去重设它的 childExpirationTime 的一个过程