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 的一个过程
相关推荐
青茶360几秒前
php怎么实现订单接口状态轮询(二)
前端·php·接口
大橙子额44 分钟前
【解决报错】Cannot assign to read only property ‘exports‘ of object ‘#<Object>‘
前端·javascript·vue.js
晚霞的不甘2 小时前
守护智能边界:CANN 的 AI 安全机制深度解析
人工智能·安全·语言模型·自然语言处理·前端框架
LYFlied2 小时前
从 Vue 到 React,再到 React Native:资深前端开发者的平滑过渡指南
vue.js·react native·react.js
爱喝白开水a2 小时前
前端AI自动化测试:brower-use调研让大模型帮你做网页交互与测试
前端·人工智能·大模型·prompt·交互·agent·rag
董世昌412 小时前
深度解析ES6 Set与Map:相同点、核心差异及实战选型
前端·javascript·es6
吃杠碰小鸡3 小时前
高中数学-数列-导数证明
前端·数学·算法
kingwebo'sZone3 小时前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_09014 小时前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农4 小时前
Vue 2.3
前端·javascript·vue.js