React 运行流程——并发不是你想的 “并发”

344 阅读4分钟

React 的并发特性

第一次知道 React 18 出了并发特性之后,我的第一反应是可以同时更新,那性能是大大的提升啊。我得赶紧把这个和领导提一下,把公司项目的 React 升到最新,性能怕是大大提升 50% 不止哦。就在找领导的路上,我脑瓜子灵光一闪,好像不对啊!js 不是单线程执行的吗?咋回事?冷静下来的我赶紧回去查阅了一下资料:

  • 并发
    并发是指可以执行多个任务,但是同时只能执行一个。举个例子: 你在掘金浏览文章,然后突然来了个线上问题,你立马就停止浏览文章去修复问题,修完之后又接着浏览文章。这个例子中,你并发了两件事,同一时间只能干一件事,一件事没干完,可以停下来干别的事,完了再接着干上一件事。
  • 并行
    并行是指可以执行多个任务,这些任务可以同时执行。举个例子,你下班回家之后,一边看电视,一边吃饭,同时就把这两件事干了,这就是并行。

原来并发不是我想的那个“并发”啊,恩,这就和 js 单线程对上了,幸好刚刚没去给领导说!

React 新架构简介

前面介绍了 React 架构分为三个部分 Scheduler(调度器)  、 Reconciler(协调器)  和 Renderer(渲染器)

在 React 的工作流程中, Reconciler 工作的阶段被称为 render 阶段, Renderer 工作的阶段被称为 commit 阶段。

Reconciler

结合前面文章的内容,Reconciler 主要的工作是构建 wip Fiber 树,并为有改动的节点打上对应的 flags(节点更新和增删等),工作完成后将构建完成的 wip Fiber 树转交给 Renderer。详细是如何工作后面会出一篇文章。
注意:render阶段是可能被打断的,React 的并发就并发在这个阶段。

Renderer

在这个阶段主要根据 Reconciler 转交过来的 wip Fiber树,将对应改动 commit(提交) 到页面上,通俗点就是在这个阶段操作 DOM 。并在不同的时机执行类组件的生命周期函数,以及函数组件的 Effect。详细是如何工作后面会出一篇文章。 注意 commit 阶段不会中断,一旦开始就会同步执行一直到完成。

Scheduler

调度器每次会选出优先级最高的任务执行,算法采用的是小顶堆。详细实现后续会单独出一篇文章。

React 运行流程

流程

个人理解:React 的运行流程分为 3 步:

  • 生成任务:通过初次渲染或者交互产生任务(render + commit 的流程)。
  • 调度任务:在已生成的任务中挑出一个最高优先级的任务。
  • 执行任务:同步或者并发执行当前任务。

下面是流程图: image.png 我们主要看看执行任务的过程中render的执行情况: 当前任务 A 如果满足并发的条件,会进入并发的render流程开启时间分片(Time Slice),通过 shouldYield() 判断每 5ms 终止一次循环并重新调度一个任务 B ,如果 A 的优先级低于 B ,就停止执行 A ,优先执行 B 任务。 如果 A的优先级和 B 一致或者 A 和 B 是同一个任务,那么继续执行 A。

当前任务 A 不满足并发的条件,则进入同步执行流程,直到 A 执行完成之后再执行下一个任务。

开启并发更新的条件

先说结论:

  • 优先级不为 sync
  • 不包含阻塞更新
  • 不包含已经过期的更新任务
  • 当前任务没有过期 以上条件同时满足即可进入并发render流程。

下面直接看看代码的判断

image.png

这段代码是初步判断能否并发执行任务,但是具体是否开启并发执行还得进一步的判断,下面看看performConcurrentWorkOnRoot 中具体的判断

image.png

并发更新总结

通过 api 开启并发更新的本质就是通过 api 降低当前更新的优先级,使其满足并发更新的条件。具体如何降低后续详解。

最后

感谢大家的阅读,有不对的地方也欢迎大家指出来。

参考

React 18.2.0 源码