【React】“车道”模型 - 区分优先级

1,766 阅读4分钟

在 React 应用中,当我们使用 setState 或者 Hooks 来更新数据状态时,都会触发重新 “渲染” 来更新应用。

之前,在 《Concurrent 的奥秘》中聊到过,并发渲染的一个特点就是: “高优先级的渲染会中断低优先级的渲染” 。那今天就来看一下 “优先级” 是如何在并发渲染中发挥作用的。

在今天的场景中,比起 “优先级” ,有个更适合的词或者理念,叫做 “车道模型”。在 React 内部使用了 Lane 这个单词,相信这篇文章可以让大家不仅停留在这个单词的直译上,而是真的理解 “车道模型”。

整体流程

我们先从整体上看一下,从 “数据变更” 到 “渲染” 的链路是怎样的:

当我们更改数据时,其实是创建了一个更新 **Update** 。这个 **Update** 上会有一个属性 **lane** ,用来标记该更新所属的 “车道” 。

接着这个 **Update** 会被添加到对应 Fiber 节点的更新队列中。

同时这个 **Update****lane** 会被标记到从 FiberFiberRootNode 的全部节点上。这样做的目的是:可以方便的从 FiberRootNode 上知道,当前一共有哪些 “车道” 的更新需要做。

最后,会将所需要的执行渲染任务,加入到调度器中等待执行。

在渲染过程中,也仅会处理 Fiber 节点上,属于本次更新 “车道” 的更新 **Update**

更新上的 “车道” 标记

先来看一下这个更新 **Update** 到底什么样的:

知道了更新是什么样子之后,我们再来看一下更新 **Update** 上的 **Lane** 是从何而来:

当我们调用 setState/Hooks 时,React 内部会调用 requestUpdateLane 接口,得到当前 **Update****Lane**

基本逻辑为:

  1. 如果本次更新处在一个 **Transition** 中,就会返回 **TransitionLane**

  2. 否则,判断 **currentUpdatePriority** 是否为 **NoLane** , 不是的情况下,再返回 **DefaultEventPriority(DefaultLane)**或者根据当前事件类型返回。

当前更新处在 **Transition** 中是指:在 startTransition 中更新数据 **setState/Hooks** 。详细的例子可以看《Concurrent 的奥秘》,关于 **startTransition** 的使用和作用可以参考 New feature: startTransition

那么关于这里可能涉及到的几个 “车道” 标记的优先级为:

**SyncLane > InputContinuousLane > DefaultLane > TransitionLane**

Fiber树上的 “车道” 标记

从前面的的整体流程中,我们知道当 **Update** 被添加到对应 Fiber 的更新队列中时,会同时把 “车道” 标记 **Lane** 记录在 Fiber树上:

具体处理为:通过 markUpdateLaneFromFiberToRoot 函数,将 **Lane** 标记在当前 FiberHostRoot 的每一级节点的 **childLanes** , 同时将 **Lane** 标记在 FiberRootNode**pendingLanes** 上。

前边我们提到过,将 **Lane** 标记在 FiberRootNode 上,是可以让我们快速通过顶层节点知道,本次 “渲染” 需要对 Fiber树 做哪些 “车道的更新” 。

那为什么要把 **Lane** 标记在每一级节点的 **childLanes** 上呢?

其实,将 **Lane** 标记在每一级节点上,是方便在 “渲染” 时,知道每个 Fiber 下的子树是否需要处理当前 “车道” 的更新,如果不需要,就可以跳过,提升处理效率。

渲染中的 “车道” 标记

通过前边两部分,我们已经知道了在创建 **Update** 时,**Lane** 从何而来,以及它如何被记录的。

那么当 “渲染” 时,也就是 React 需要刷新 Fiber树 ,处理这些 **Update** 时,“车道” **Lane** 是如何发挥作用的呢?

每次 “渲染” FiberRootNode 上获取优先级最高**Lane** 作为本次渲染的 **renderLanes** ,接着在处理 Fiber树 时,也仅会处理符合 **renderLanes****Update**,不符合的将会被跳过。

完成一次 "渲染" 后,会检查 Fiber树 上是否还有未处理的 **Update** , 如果有,就会将渲染任务加入调度器等待再次 “渲染”。

关于调度器的更多内容,可以查看《调度系统 - Scheduler》

总结

当我们更改 React 应用的状态时,会创建不同 “车道” 的 **Update**,React 也会按照优先顺序来处理每个 “车道” 的 **Update**

同时,通过:**高优先级的渲染会优先于低优先级的渲染,甚至会中断低优先级的渲染。**通过这样设计,React 为开发者提供了一种 “让局部优先完成渲染” 的能力,可以用来改善我们应用的用户体验。关于这一部分,感兴趣的同学可以通过《Concurrent 的奥秘》了解更多。