默认使用新架构的 React Native 0.76 已发布

234 阅读13分钟

默认使用新架构的 React Native 0.76 现已在 npm 上发布!

在 0.76 发布博文中,我们分享了该版本中包含的重大变更列表。在本篇博文中,我们将概述新架构及其如何塑造 React Native 的未来。

新架构增加了对现代 React 功能的全面支持,包括 Suspense、Transitions、自动批处理和 useLayoutEffect。新架构还包括新的原生模块(Native Module)和原生组件(Native Component)系统,让您可以编写类型安全的代码,无需桥接即可直接访问原生接口。

这个版本是我们自 2018 年以来对 React Native 进行的一次彻底重写的结果,我们格外注意让新架构对大多数应用来说都是渐进式迁移。2021 年,我们创建了新架构工作组(New Architecture Working Group),与社区合作确保整个 React 生态系统都能获得流畅的升级体验。

大多数应用程序都能像采用其他版本一样采用 React Native 0.76。最流行的 React Native 库已经支持新架构。新架构还包含一个自动互操作层,可实现与旧架构库的向后兼容。

什么是新架构

新架构是对支撑 React Native 的主要系统的全面重写,包括组件的呈现方式、JavaScript 抽象与原生抽象之间的通信方式,以及不同线程之间的工作调度方式。虽然大多数用户不需要考虑这些系统是如何工作的,但这些变化带来了改进和新功能。

在旧架构中,React Native 通过异步桥与原生平台通信。要渲染一个组件或调用一个原生函数,React Native 需要将原生函数调用序列化并在桥接器上排序,然后进行异步处理。这种架构的好处是,主线程在渲染更新或处理原生模块函数调用时绝不会阻塞,因为所有工作都是在后台线程上完成的。

但是,用户希望交互能得到即时反馈,这样才会有原生应用的感觉。这意味着某些更新需要同步呈现以响应用户输入,从而可能中断任何正在进行的呈现。由于旧架构仅支持异步,因此我们需要重写,以便同时支持异步和同步更新。

此外,在旧架构中,通过桥接器对函数调用进行序列化很快就会成为瓶颈,尤其是对于频繁更新或大型对象。这使得应用程序很难稳定地达到 60+ FPS。此外还有同步问题:当 JavaScript 层和原生层不同步时,就不可能同步调和,从而导致列表显示空格和由于中间状态渲染而造成的可视化 UI 跳转等错误。

最后,由于旧架构使用本地层次结构保留了用户界面的单个副本,并对该副本进行了就地修改,因此布局只能在单线程上计算。这样就无法处理用户输入等紧急更新,也无法同步读取布局,例如读取布局效果来更新工具提示的位置。

所有这些问题都意味着无法正确支持 React 的并发功能。为了解决这些问题,新架构包括四个主要部分:

  • 新的本地模块系统
  • 新的渲染器
  • 事件循环
  • 移除桥接器

新模块系统允许 React 原生渲染器同步访问原生层,从而可以异步或同步地处理事件、安排更新和读取布局。新的原生模块在默认情况下也是懒加载的,从而为应用程序带来显著的性能提升。

新渲染器可以跨多个线程处理多个进行中的树,这使得 React 可以在主线程或后台线程上处理多个并发更新优先级。它还支持同步或异步从多个线程读取布局,以支持更灵敏的用户界面,而不会出现抖动。

新的事件循环可以按照明确定义的顺序处理 JavaScript 线程上的任务。这使得 React 可以中断呈现来处理事件,因此紧急用户事件可以优先于优先级较低的 UI 过渡。事件循环还符合网络规范,因此我们可以支持微任务、MutationObserver 和 IntersectionObserver 等浏览器功能。

最后,移除桥接器可以加快启动速度,并在 JavaScript 和本地运行时之间进行直接通信,从而将切换工作的成本降至最低。这样还能更好地进行错误报告和调试,减少因未定义行为而导致的崩溃。

新架构现在已经可以用于生产。它已在 Meta 的 Facebook 应用程序和其他产品中大规模使用。我们在为 Quest 设备开发的 Facebook 和 Instagram 应用程序中成功使用了 React Native 和新架构。

我们的合作伙伴已经在生产中使用新架构几个月了:请看看 Expensify 和 Kraken 的成功案例,也可以试试 BlueSky 的新版本。

新的本地模块

新的本地模块系统对 JavaScript 与本地平台的通信方式进行了重大重写。它完全由 C++ 编写,因此能释放出许多新功能:

  • 与本地运行时的同步访问
  • JavaScript 与本地代码之间的类型安全
  • 跨平台代码共享
  • 默认情况下的懒模块加载

在新的本地模块系统中,JavaScript 和本地层现在可以通过 JavaScript 接口 (JSI) 进行同步通信,而无需使用异步桥接。这意味着您的自定义本地模块现在可以同步调用一个函数,返回一个值,并将该值传回给另一个本地模块函数。

在旧架构中,为了处理本地函数调用的响应,需要提供一个回调,而且返回的值必须是可序列化的:

// ❌ 本地模块的同步回调
nativeModule.getValue(value => {
  // ❌ 值不能引用本地对象
  nativeModule.doSomething(value);
});

在新架构中,可以同步调用本地函数:

// ✅ 本地模块的同步响应
const value = nativeModule.getValue();

// ✅ value 可以是对本地对象的引用
nativeModule.doSomething(value);

有了新架构,你终于可以充分利用 C++ 本机实现的全部功能,同时还能通过 JavaScript/TypeScript API 访问它。新模块系统支持用 C++ 编写的模块,因此您只需编写一次模块即可,而且它适用于所有平台,包括 Android、iOS、Windows 和 macOS。用C++实现模块可以实现更精细的内存管理和性能优化。

此外,使用Codegen,你的模块可以在JavaScript层和本地层之间定义强类型契约。根据我们的经验,跨边界类型错误是跨平台应用程序崩溃的最常见原因之一。Codegen 可以帮你克服这些问题,同时还能为你生成模板代码。

最后,模块现在是懒加载的:它们只在有效需要时才加载到内存中,而不是在启动时加载。这缩短了应用程序的启动时间,并随着应用程序复杂度的增加而保持较低的启动时间。

react-native-mmkv 等流行库已经从迁移到新的本地模块中获益:

“新的本地模块大大简化了 react-native-mmkv 的设置、自动链接和初始化。得益于新架构,react-native-mmkv 现在是一个纯 C++ 原生模块,可以在任何平台上运行。新的 Codegen 允许 MMKV 实现完全的类型安全,通过强制执行空安全,解决了长期存在的 NullPointerReference 问题,并且能够同步调用原生模块函数,使我们能够用新的原生模块 API 取代自定义的 JSI 访问。

react-native-mmkv 创建者 Marc Rousavy

新的渲染器

我们还完全重写了原生渲染器,增加了多项优势:

更新可以在不同的线程上以不同的优先级进行渲染。 可以跨不同线程同步读取布局。 渲染器使用 C++ 编写,可在所有平台上共享。 更新后的本地渲染器将视图层次结构存储在不可变的树形结构中。这意味着用户界面的存储方式无法直接更改,从而允许以线程安全的方式处理更新。这样,它就能处理多个进行中的树,每个树代表不同版本的用户界面。因此,更新可以在后台呈现,而不会阻塞用户界面(如在转换过程中)或主线程(响应用户输入)。

通过支持多线程,React 可以中断低优先级更新以呈现紧急更新,例如由用户输入生成的更新,然后根据需要恢复低优先级更新。新的呈现器还可以跨线程同步读取布局信息。这样,低优先级更新就可以进行后台计算,需要时还可以同步读取,例如重新定位工具提示。

最后,用 C++ 重写呈现器可以在所有平台上共享。这确保了相同的代码能在 iOS、Android、Windows、macOS 和任何其他支持 React Native 的平台上运行,从而提供一致的呈现功能,而无需为每个平台重新实现。

这是我们向多平台愿景迈出的重要一步。例如,“视图扁平化”(View Flattening)是一种仅适用于 Android 的优化,以避免深度布局树。采用共享 C++ 内核的新渲染器为 iOS 带来了这一功能。这种优化是自动的,无需设置,共享呈现器免费提供。

有了这些变化,React Native 现在可以完全支持并发 React 功能(如 Suspense 和 Transitions),从而可以更轻松地构建复杂的用户界面,快速响应用户输入,而不会出现抖动、延迟或视觉跳跃。未来,我们将利用这些新功能为 FlatList 和 TextInput 等内置组件带来更多改进。

Reanimated 等流行库已经开始利用新渲染器:

“目前正在开发中的 Reanimated 4 引入了一个新的动画引擎,该引擎可直接与新渲染器配合使用,使其能够跨不同线程处理动画和管理布局。新渲染器的设计真正实现了这些功能,而无需依赖大量的变通方法。此外,由于它是用 C++ 实现并跨平台共享的,Reanimated 的大部分内容都可以一次编写完成,从而减少了特定平台的问题,最大限度地减少了代码库,并简化了树外平台的采用。

Krzysztof Magiera,Reanimated 的创建者

事件循环

新架构允许我们实现一个定义明确的事件循环处理模型,如本 RFC 所述。本 RFC 遵循 HTML 标准中描述的规范,描述了 React Native 应如何在 JavaScript 线程上执行任务。

实现定义明确的事件循环缩小了 React DOM 和 React Native 之间的差距:React Native 应用程序的行为现在更接近于 React DOM 应用程序的行为,从而更易于一次性学习和随处编写。

事件循环为 React Native 带来了许多好处:

  • 中断呈现以处理事件和任务的能力
  • 更符合网络规范
  • 为更多浏览器功能奠定基础 有了事件循环,React 就能对更新和事件进行可预测的排序。这使得 React 可以用紧急用户事件中断低优先级的更新,而新渲染器则允许我们独立渲染这些更新。

事件循环还将事件和任务(如计时器)的行为与网络规范相统一,这意味着 React Native 的工作方式与用户所熟悉的网络工作方式更加相似,并允许 React DOM 和 React Native 之间更好地共享代码。

它还允许实现更多兼容浏览器的功能,如微任务、MutationObserver 和 IntersectionObserver。这些功能还不能在 React Native 中使用,但我们正努力在将来为您提供这些功能。

最后,“事件循环”(Event Loop)和 “新渲染器”(New Renderer)的更改支持同步读取布局信息,这使得 React Native 可以添加对 useLayoutEffect 的适当支持,从而同步读取布局信息并在同一帧中更新 UI。这样,您就可以在元素显示给用户之前将其正确定位。

更多详情,请参阅 useLayoutEffect。

移除桥接器

在新架构中,我们还完全移除了 React Native 对桥接器的依赖,取而代之的是使用 JSI 在 JavaScript 和本地代码之间进行直接、高效的通信:

image.png

移除桥接器可以避免桥接器初始化,从而缩短启动时间。例如,在旧架构中,为了向 JavaScript 提供全局方法,我们需要在启动时初始化 JavaScript 中的一个模块,这会导致应用启动时间的小幅延迟:

// ❌ 初始化缓慢
import {NativeTimingModule} from 'NativeTimingModule'global.setTimeout = timer => {
  NativeTimingModule.setTimeout(timer);
};

// App.js
setTimeout(() => {}, 100);

在新架构中,我们可以直接绑定 C++ 方法:

// ✅ 直接在 C++ 中初始化
runtime.global().setProperty(runtime, “setTimeout”, createTimer);

// App.js
setTimeout(() => {}, 100);

重写还改进了错误报告,尤其是针对启动时 JavaScript 崩溃的错误报告,并减少了因未定义行为而导致的崩溃。如果发生崩溃,新的 React Native DevTools 可简化调试并支持新架构。

保留桥接代码是为了向后兼容,以支持逐步迁移到新架构。未来,我们将完全删除桥接代码。

逐步迁移

我们希望大多数应用程序都能像升级其他版本一样升级到 0.76。

升级到 0.76 时,新架构和 React 18 默认已启用。但是,要使用并发功能并获得新架构的全部优势,您的应用程序和库需要逐步迁移以完全支持新架构。

首次升级时,您的应用程序将在新架构上运行,并带有一个与旧架构的自动互操作层。对于大多数应用程序来说,无需做任何更改即可运行,但互操作层存在已知的限制,因为它不支持访问自定义阴影节点或并发功能。

要使用并发功能,应用程序还需要根据 React 规则进行更新,以支持并发 React。要将 JavaScript 代码移植到 React 18 及其语义,请遵循 React 18 升级指南。

总体策略是在不破坏现有代码的情况下,让您的应用在新架构上运行。然后,您可以按照自己的节奏逐步迁移应用程序。对于已将所有模块迁移到新架构的新表面,您可以立即开始使用并发功能。对于现有界面,您可能需要先解决一些问题并迁移模块,然后再添加并发功能。

我们已与最流行的 React Native 库合作,以确保对新架构的支持。超过 850 个库已经兼容,包括所有每周下载量超过 20 万次的库(约占下载库的 10%)。您可以在 reactnative.directory 网站上查看库与新架构的兼容性。

image.png

更多详情请访问 这里