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, 直接到 elseif (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的子树里面任何几个节点它所创建的更新所对应的优先级最高的 expirationTimelet child = workInProgress.child;while (child !== null) {// 为什么这边要用这两个值?因为我们没有办法直接拿到当前节点它所有子树最高优先级的点// 只能是通过去遍历它的所有的第一层的子节点以及每个子节点它的 childExpirationTime// 因为我们这个 completeUnitOfWork 是由底往上去更新的一个过程// 那么由底往上更新的过程,就会每一个节点都会对应这个操作// 对应的这个操作之后,它肯定就是每一个节点都会更新到优先级最高的那个 expirationTimeconst 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 的一个过程