React 从 v15 升级到 v16 后,为什么要重构底层架构

博文小编

2022-12-16

【本文原创:卡颂】

React 从 v15 升级到 v16 后重构了整个架构,v16 及以上版本一直沿用新架构,重构的主要原因在于:旧架构无法实现 Time Slice。

01 新旧架构介绍

React15 架构可以分为两部分:

Reconciler(协调器)——VDOM 的实现,负责根据自变量变化计算出 UI 变化。

Renderer(渲染器)——负责将 UI 变化渲染到宿主环境中。

在 Reconciler 中,mount 的组件会调用 mountComponent,update 的组件会调用updateComponent,这两个方法都会递归更新子组件,更新流程一旦开始,中途无法中断。

基于这个原因,React16 重构了架构。重构后的架构一直沿用至今,可以分为 3 部分:

Scheduler(调度器)——调度任务的优先级,高优先级任务优先进入 Reconciler。

Reconciler(协调器)——VDOM 的实现,负责根据自变量变化计算出 UI 变化。

Renderer(渲染器)——负责将 UI 变化渲染到宿主环境中。

在新架构中,Reconciler 中的更新流程从递归变成了“可中断的循环过程”。每次循环都会调用 shouldYield 判断当前 Time Slice 是否有剩余时间,没有剩余时间则暂停更新流程,将主线程交给渲染流水线,等待下一个宏任务再继续执行,这就是 Time Slice的实现原理:

shouldYield 方法如下:

过期时间 deadline 在任务执行时被更新为“当前时间+时间间隔”,时间间隔默认为5ms,这也是图 2-3 中每个 Time Slice 宏任务的时间长度是 5ms 左右的原因。

当 Scheduler 将调度后的任务交给 Reconciler 后,Reconciler 最终会为 VDOM 元素标记各种副作用 flags,比如:

Scheduler 与 Reconciler 的工作都在内存中进行。只有当 Reconciler 完成工作后,工作流程才会进入 Renderer。

Renderer 根据“Reconciler 为 VDOM 元素标记的各种 flags”执行对应操作,比如,如上三个 flags 在浏览器宿主环境中对应三种 DOM 操作。

下面的示例1演示了上述三个模块如何配合工作:count 默认值为 0,每次点击按钮执行 count++,UL 中三个 LI 的内容分别为“1、2、3 乘以 count 的结果”。

示例1:

对应工作流程如图1所示。

虚线框中的工作流程随时可能由于以下原因被中断:

 有其他更高优先级任务需要先执行;

 当前 Time Slice 没有剩余时间;

 发生错误。


图 1 新 React 架构工作流程示例

由于虚线框内的工作都在内存中进行,不会更新宿主环境 UI,因此即使工作流程反复中断,用户也不会看到“更新不完全的 UI”。

02 主打特性的迭代

随着 React 架构的重构,上层主打特性也随之迭代。按照“主打特性”划分,React大体经历了四个发展时期:
(1)Sync(同步);

(2)Async Mode(异步模式);

(3)Concurrent Mode(并发模式);

(4)Concurrent Feature(并发特性)

其中,旧架构对应同步时期。异步模式、并发模式、并发特性三个时期与新架构相关。本节主要讲解异步模式、并发模式、并发特性的演进过程。

之前曾提到“CPU 瓶颈”与“I/O 瓶颈”,React 并不是同时解决这两个问题的。首先解决的是“CPU 瓶颈”,解决方式是“架构重构”。重构后Reconciler 的工作流程从“同步”变为“异步、可中断”。正因如此,这一时期的 React被称为 Async Mode。

单一更新的工作流程变为“异步、可中断”并不能完全突破“I/O 瓶颈”,解决问题的关键在于“使多个更新的工作流程并发执行”。所以,React 继续迭代为 Concurrent Mode(并发模式)。在 React 中,Concurrent(并发)概念的意义是“使多个更新的工作流程可以并发执行”。

以上便是从 Sync 到 Async Mode 再到 Concurrent Mode 的演进过程。下一节将讲解从 Concurrent Mode 到 Concurrent Feature 的演进过程。

03 渐进升级策略的迭代

从最初的版本到 v18 版本,React 有多少个版本?从架构角度进行概括,所有React版本一定属于如下四种情况之一。

情况 1:旧架构(v15 及之前版本属于这种情况)。

情况 2:新架构,未开启并发更新,与情况 1 行为一致(v16、v17 默认属于这种情况)。

情况 3:新架构,未开启并发更新,但是启用了一些新功能(比如 AutomaticBatching)。

情况 4:新架构,已开启并发更新。

React 团队希望:使用旧版本的开发者可以逐步升级到新版本,即从情况 1、2、3向情况 4 升级。但是升级过程中存在较大阻力,因为在情况 4 下,React 的一些行为与情况 1、2、3 不同。比如以下三个生命周期函数在情况 4 的 React 下是“不安全的”:

componentWillMount

componentWillReceiveProps

componentWillUpdate

强制升级可能造成代码不兼容。为了使 React 的新旧版本之间实现平滑过渡,React团队采用了“渐进升级”方案。该方案的第一步是规范代码。v16.3 新增了 StrictMode,针对开发者编写的“不符合并发更新规范的代码”给出提示,逐步引导开发者编写规范代码。比如,使用上述“不安全的”生命周期函数时会产生如图2所示的报错信息。

图2 StrictMode 下使用不安全生命周期函数报错

下一步,React 团队允许“不同情况的 React”在同一个页面共存,借此使“情况 4的 React”逐步渗透至原有项目中。具体做法是提供了以下三种开发模式:

1.Legacy 模式,通过 ReactDOM.render(, rootNode)创建的应用遵循该模式。默认关闭 StrictMode,表现同情况 2。

2.Blocking模式 通过 ReactDOM.createBlockingRoot(rootNode).render()创建的应用遵循该模式,作为从 Legacy 向 Concurrent 过渡的中间模式,默认开启StrictMode,表现同情况 3。

3.Concurrent 模式,通过 ReactDOM.createRoot(rootNode).render()创建的应用遵循该模式,默认开启 StrictMode,表现同情况 4。

三种开发模式支持特性对比如图3所示

图3 三种开发模式支持特性对比

为了使不同模式的应用可以在同一个页面内工作,需要对一些底层实现进行调整。比如:调整之前,大多数事件会统一冒泡到 HTML 元素,调整后则冒泡到“应用所在根元素”。这些调整工作发生在 v17,所以v17 也被称作“为开启并发更新做铺垫”的“垫脚石”版本。

2021 年 6 月 8 日,v18 工作组成立。在与社区进行大量沟通后,React 团队意识到当前的“渐进升级”策略存在两方面问题。首先,由于模式影响的是整个应用,因此无法在同一个应用中完成渐进升级。举例说明,开发者将应用中 ReactDOM.render 改为ReactDOM.createBlockingRoot,从 Legacy 模式切换到 Blocking 模式,会自动开启StrictMode。此时,整个应用的“并发不兼容警告”都会上报,开发者需要修复整个应用中的不兼容代码。从这个角度看,“渐进升级”的目的并没有达到。

其次,React 团队发现:开发者从新架构中获益,主要是由于使用了并发特性,并发特性指“开启并发更新后才能使用的那些 React 为了解决 CPU 瓶颈、I/O 瓶颈而设计的特性”,比如:

useDeferredValue

useTransition

所以,React 团队提出新的渐进升级策略——开发者仍可以在默认情况下使用同步更新,在使用并发特性后再开启并发更新。

在 v18 中运行示例2所示代码,由于 updateCount 在 startTransition 的回调函数中执行(使用了并发特性),因此 updateCount 会触发并发更新。如果 updateCount 没有在startTransition 的回调函数中执行,那么 updateCount 将触发默认的同步更新。

示例2:

读者可以调试在线示例中这两种情况的调用栈火焰图,根据火焰图中观察到的 “是否开启 Time Slice”来区分“是否是并发更新”。

所以,React 在 v18 中不再提供三种开发模式,而是以“是否使用并发特性”作为“是否开启并发更新”的依据。

具体来说,开发者在 v18 中统一使用 ReactDOM.createRoot 创建应用。当不使用并发特性时,表现如情况 3。使用并发特性后,表现如情况 4。


本文节选自卡颂的新书《React设计原理》,基于React18,从理念、架构、实现三个层面解构React。

这本书存在两条脉络:

抽象层级逐渐降低

实现越来越复杂的模块

对于前者,本书的抽象层级会逐渐从理念到架构,最后到实现,每一层都屏蔽前一层的影响。

这也是为什么ReactDOM.createRoot这个初始化API会放到第六章再讲解 —— 在这个具体API的背后,是他的理念与架构。

对于后者,本书会从0实现与react相关的6个模块,最后我们会一起在React源码内实现一个新的原生Hook


扫码查看本书详情!

读者评论

相关博文

  • 社区使用反馈专区

    陈晓猛 2016-10-04

    尊敬的博文视点用户您好: 欢迎您访问本站,您在本站点访问过程中遇到任何问题,均可以在本页留言,我们会根据您的意见和建议,对网站进行不断的优化和改进,给您带来更好的访问体验! 同时,您被采纳的意见和建议,管理员也会赠送您相应的积分...

    陈晓猛 2016-10-04
    5436 739 3 7
  • 迎战“双12”!《Unity3D实战核心技术详解》独家预售开启!

    陈晓猛 2016-12-05

    时隔一周,让大家时刻挂念的《Unity3D实战核心技术详解》终于开放预售啦! 这本书不仅满足了很多年轻人的学习欲望,并且与实际开发相结合,能够解决工作中真实遇到的问题。预售期间优惠多多,实在不容错过! Unity 3D实战核心技术详解 ...

    陈晓猛 2016-12-05
    3299 36 0 1
  • czk 2017-07-29
    5871 28 0 1