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
对应到代码里面,来看在
马上会执行
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 的一个过程