在 React 应用中,当我们使用 setState 或者 Hooks 来更新数据状态时,都会触发重新 “渲染” 来更新应用。
之前,在 《Concurrent 的奥秘》中聊到过,并发渲染的一个特点就是: “高优先级的渲染会中断低优先级的渲染” 。那今天就来看一下 “优先级” 是如何在并发渲染中发挥作用的。
在今天的场景中,比起 “优先级” ,有个更适合的词或者理念,叫做 “车道模型”。在 React 内部使用了 Lane 这个单词,相信这篇文章可以让大家不仅停留在这个单词的直译上,而是真的理解 “车道模型”。
整体流程
我们先从整体上看一下,从 “数据变更” 到 “渲染” 的链路是怎样的:
当我们更改数据时,其实是创建了一个更新 **Update** 。这个 **Update** 上会有一个属性 **lane** ,用来标记该更新所属的 “车道” 。
接着这个 **Update** 会被添加到对应 Fiber 节点的更新队列中。
同时这个 **Update** 的 **lane** 会被标记到从 Fiber 到 FiberRootNode 的全部节点上。这样做的目的是:可以方便的从 FiberRootNode 上知道,当前一共有哪些 “车道” 的更新需要做。
最后,会将所需要的执行渲染任务,加入到调度器中等待执行。
在渲染过程中,也仅会处理 Fiber 节点上,属于本次更新 “车道” 的更新 **Update** 。
更新上的 “车道” 标记
先来看一下这个更新 **Update** 到底什么样的:
知道了更新是什么样子之后,我们再来看一下更新 **Update** 上的 **Lane** 是从何而来:
当我们调用 setState/Hooks 时,React 内部会调用 requestUpdateLane 接口,得到当前 **Update** 的 **Lane** 。
基本逻辑为:
-
如果本次更新处在一个
**Transition**中,就会返回**TransitionLane**。 -
否则,判断
**currentUpdatePriority**是否为**NoLane**, 不是的情况下,再返回**DefaultEventPriority(DefaultLane)**或者根据当前事件类型返回。
当前更新处在
**Transition**中是指:在startTransition中更新数据**setState/Hooks**。详细的例子可以看《Concurrent 的奥秘》,关于**startTransition**的使用和作用可以参考 New feature: startTransition 。
那么关于这里可能涉及到的几个 “车道” 标记的优先级为:
**SyncLane > InputContinuousLane > DefaultLane > TransitionLane**
Fiber树上的 “车道” 标记
从前面的的整体流程中,我们知道当 **Update** 被添加到对应 Fiber 的更新队列中时,会同时把 “车道” 标记 **Lane** 记录在 Fiber树上:
具体处理为:通过 markUpdateLaneFromFiberToRoot 函数,将 **Lane** 标记在当前 Fiber 到 HostRoot 的每一级节点的 **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 的奥秘》了解更多。