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 的一个过程
相关推荐
我要洋人死1 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人1 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人1 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR1 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_9152 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼3 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
小牛itbull4 小时前
ReactPress:重塑内容管理的未来
react.js·github·reactpress