expirationTime 和 nextExpirationTimeToWorkOn
1 )概述
-
这两个值在整个 render 以及 commit 的过程当中,都起着非常重要的一个作用
-
为什么react又需要去设置两个值来制定一些优先级相关的内容?
-
expirationTime 作用在渲染之前的,而 nextExpirationTimeToWorkOn 则是作用在渲染时的
-
为什么说 expirationTime 是作用在渲染前的呢?
- 因为 expirationTime 它发生作用的地方
- 比如说在 requestWork 的时候,去调用
scheduleCallbackWithExpirationTime(root, expirationTime);
- 这个时候就是传入的 expirationTime 是 root.expirationTime
- 往前找, 在 scheduleWork 中 调用requestWork时,这里可以验证
const rootExpirationTime = root.expirationTime; requestWork(root, rootExpirationTime)
- scheduleCallbackWithExpirationTime 这个函数
- 是拿这个 expirationTime 参数来计算出设置在 reactschedule 里面的
- 这个时间片更新的 callback,它在什么时间点是过期的一个行为
- 而在 requestWork 里面,判断是否要去调用异步的时间片更新的方式,还是通过同步的方式去更新
- 就是根据判断 expirationTime 是否等于 Sync 来进行的, 参考这里
- 所以在这个过程当中,可以发现 root.expirationTime 决定了是否使用异步的方式来进行一个react的更新
- 那么异步的方式跟同步的方式区别
- 就是会拿到一个 deadline
- 在我们执行 performWork 的时候,有一个 deadline 的一个对象
- 在进行 workLoop的时候,是要每执行完一个单元要来进行判断一下这个 deadlineobject 是否已经过期
- 而对于我们调用 performSyncWork 来进行的更新是完全没有这个考虑的
- 是一条路走到黑,整棵树更新完之后,然后立马进行一个提交
- 中间没有一个可以中断的一个过程
- 在调用 performSyncWork 的时候,最终调用了 performWork
- 注意,在 16.6.3 这个版本及后续版本,引入了 isYieldy 属性来替代 deadline 对象
- isYieldy 更加简单,只是一个布尔值,用于指示当前任务是否可以被中断
- 然后传入的是 Sync 以及 后面 isYieldy 为 false, 对于 false 时,进入下面的判断和while循环,参考
- 这里,performWorkOnRoot 的第三个参数 isYieldy 是 写死的 false, 在函数内部直接传递给 renderRoot 函数
- expirationTime 的作用主要发生进入更新新前,也就是进入 performWork 之前
- 来确定是否要使用时间片的更新方式来进行一个react的整体的渲染
- 使用时间片的更新方式会让整体的页面上面的一些性能的分配会更合理一点
- 比如说把更高的优先级留给浏览器,要展现动画要进行一些计算
- 而对于react,认为这个任务是比较低优先级的,所以可以分几次来进行一个完成
-
而 nextExpirationTimeToWorkOn 有什么作用呢?
- nextExpirationTimeToWorkOn 只出现在 renderRoot 中出现2次
- 第一次是在 函数开始,它设置了一个变量,
const expirationTime = root.nextExpirationTimeToWorkOn;
- 第二次出现了是在 函数的结尾, 在这个条件下
else if (!root.didError && isYieldy){}
下设置const suspendedExpirationTime = (root.nextExpirationTimeToWorkOn = expirationTime);
- 它是在更新的过程当中,来确定我们这一次渲染更新的过程,它的任务的优先级的
- 可以看一下,一开始拿到了这个 expirationTime 之后,接下去怎么走
- 它会比较
if (expirationTime !== nextRenderExpirationTime || root !== nextRoot || nextUnitOfWork === null) {}
- 只要符合这个条件,就会设置
nextRenderExpirationTime = expirationTime;
- 可以看到 nextRenderExpirationTime 它是一个公共全局变量,在不同的条件下赋予记录不同的值
- 也就是在满足这个条件下,nextRenderExpirationTime 就是之前的 root.nextExpirationTimeToWorkOn
- 接下去,看 nextRenderExpirationTime 这个变量在哪些地方发挥作用
- 在 performUnitOfWork 中,调用 beginWork 中, 会传入这个 nextRenderExpirationTime
- 这个参数在 beginWork 的时候,它会有什么用呢?
function beginWork(current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime){}
- 从上面到这里,可知道,这里传进来的 renderExpirationTime 就是 上面的
root.nextExpirationTimeToWorkOn
- 这个值有一个非常关键的作用,就是用来比较我们当前的节点是否有任务
- 在这一次渲染周期当中要被执行,在这边它获取了每一个节点上面的 expirationTime
- 即:
const updateExpirationTime = workInProgress.expirationTime;
- 即:
- 每个节点的 expirationTime 跟 root 的 expirationTime 是完全不一样的
- 每个 fiber 节点的 expirationTime 只是用来记录当前这个节点上面
- 比如说像是 class component 上面创建的所有的update中优先级最高的那个update
- 也就是它的 updateExpirationTime 就相当于是说这个节点上面最高优先级的任务
- 而通过对比 root.nextExpirationTimeToWorkOn,如果在这个节点上
- 它优先级最高的那个 expirationTime 仍然要小于 renderExpirationTime (root.nextExpirationTimeToWorkOn)
- 那么它是可以直接被跳过这次更新的,跳过更新的标志是下面的
return bailoutOnAlreadyFinishedWork
if (oldProps === newProps && !hasLegacyContextChanged() && updateExpirationTime < renderExpirationTime) {}
- 因为只要符合其中一个,这个更新就可以被跳过
- 所以说 nextExpirationTimeToWorkOn,它是用来标志这一次更新当中的任务的优先级的
- 通过对比它跟每一个节点自身的 expirationTime
- 它就可以判断这个节点是否在这一次更新流程当中,需要被更新,需要被执行
- 只要符合这个条件,就会设置
- 它会比较
-
基于以上,有时候可以通过强制指定,比如强制指定 root.expirationTime = Sync
-
让它在下一次执行更新之前来使用 performSyncWork 这种方式来通过同步的方式去渲染任务
-
比如说, 在 renderRoot 中 参考
const suspendedExpirationTime = (root.nextExpirationTimeToWorkOn = expirationTime); const rootExpirationTime = (root.expirationTime = Sync);
- 通过这两个指定,在下一次更新,就是下一次回到 performWork 的循环当中的时候
- 下一个被更新到的 root 仍然是这个 root,并且它的更新模式是要通过 Sync 的模式来进行更新
- 同时它要更新的任务的优先级,仍然是我们这一次更新的任务的优先级
-
所以这就是这两个值它的作用, 通过指定它们来指定更新模式以及更新任务的优先级