React16源码: React中的resetChildExpirationTime的源码实现

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 那么这个方法它具体做了什么呢?

js 复制代码
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 的一个过程
相关推荐
牧羊狼的狼2 小时前
React 中的 HOC 和 Hooks
前端·javascript·react.js·hooks·高阶组件·hoc
知识分享小能手4 小时前
React学习教程,从入门到精通, React 属性(Props)语法知识点与案例详解(14)
前端·javascript·vue.js·学习·react.js·vue·react
魔云连洲4 小时前
深入解析:Vue与React的异步批处理更新机制
前端·vue.js·react.js
mCell4 小时前
JavaScript 的多线程能力:Worker
前端·javascript·浏览器
超级无敌攻城狮6 小时前
3 分钟学会!波浪文字动画超详细教程,从 0 到 1 实现「思考中 / 加载中」高级效果
前端
excel7 小时前
用 TensorFlow.js Node 实现猫图像识别(教学版逐步分解)
前端
gnip7 小时前
JavaScript事件流
前端·javascript
小菜全7 小时前
基于若依框架Vue+TS导出PDF文件的方法
javascript·vue.js·前端框架·json
赵得C7 小时前
【前端技巧】Element Table 列标题如何优雅添加 Tooltip 提示?
前端·elementui·vue·table组件
wow_DG7 小时前
【Vue2 ✨】Vue2 入门之旅 · 进阶篇(一):响应式原理
前端·javascript·vue.js