Skip to main content

New Architecture is here

·22 min read
The React Team
The React Team
@reactjs / @reactnative

React Native 0.76 with the New Architecture by default is now available on npm!

In the 0.76 release blog post, we shared a list of significant changes included in this version. In this post, we provide an overview of the New Architecture and how it shapes the future of React Native.

The New Architecture adds full support for modern React features, including SuspenseTransitions自动批处理useLayoutEffect。新架构还包括新的 原生模块原生组件 系统,让您可以编写类型安全的代码,并直接访问原生接口,而无需桥接。

此版本是我们自 2018 年以来一直在开发的 React Native 全新重写的结果,我们格外注意使新架构成为大多数应用程序的渐进式迁移。在 2021 年,我们创建了 新架构工作组,与社区合作,以确保整个 React 生态系统的平滑升级体验。

大多数应用程序将能够以与其他任何版本相同的精力来采用 React Native 0.76。最流行的 React Native 库已经支持新架构。新架构还包括一个自动互操作层,以实现与以旧架构为目标的库的向后兼容性。

在过去几年的开发中,我们的团队公开分享了我们对新架构的愿景。如果您错过了这些演讲中的任何一个,请在此处查看

什么是新架构

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

在旧架构中,React Native 使用异步桥接器与原生平台通信。为了渲染组件或调用原生函数,React Native 需要序列化原生函数调用并将其排队到桥接器中,然后异步处理。这种架构的优点是主线程永远不会因为渲染更新或处理原生模块函数调用而阻塞,因为所有工作都在后台线程中完成。

但是,用户期望交互得到即时反馈,以感觉像原生应用程序。这意味着某些更新需要同步渲染以响应用户输入,可能会中断任何正在进行的渲染。由于旧架构仅是异步的,因此我们需要重写它以允许异步和同步更新。

此外,在旧架构中,通过桥接器序列化函数调用很快成为瓶颈,尤其是对于频繁更新或大型对象。这使得应用程序难以可靠地实现 60+ FPS。还存在同步问题:当 JavaScript 和原生层不同步时,无法同步协调它们,导致诸如列表显示空白帧以及由于中间状态渲染而导致视觉 UI 跳跃等错误。

最后,由于旧架构使用原生层级结构保留了 UI 的单个副本,并在原地修改该副本,因此布局只能在单个线程上计算。这使得无法处理紧急更新(如用户输入),并且无法同步读取布局,例如在布局效果中读取以更新工具提示的位置。

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

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

新的模块系统允许 React Native 渲染器同步访问原生层,这使其能够处理事件、调度更新以及异步和同步读取布局。新的原生模块默认也是延迟加载的,这为应用程序带来了显着的性能提升。

新的渲染器可以处理跨多个线程的多个正在进行中的树,这允许 React 处理多个并发更新优先级,无论是在主线程还是后台线程上。它还支持从多个线程同步或异步读取布局,以支持更灵敏的 UI,而不会出现卡顿。

新的事件循环可以按明确定义的顺序处理 JavaScript 线程上的任务。这允许 React 中断渲染以处理事件,以便紧急用户事件可以优先于较低优先级的 UI 转换。事件循环也符合 Web 规范,因此我们可以支持微任务、MutationObserverIntersectionObserver 等浏览器功能。

最后,移除桥接器可以加快启动速度,并在 JavaScript 和原生运行时之间实现直接通信,从而最大限度地降低切换工作的成本。这也允许更好的错误报告、调试和减少由未定义行为引起的崩溃。

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

我们的合作伙伴已经使用新架构在生产环境中运行了数月:请查看 ExpensifyKraken 的这些成功案例,并尝试 Bluesky 的新版本。

新的原生模块

新的原生模块系统是对 JavaScript 和原生平台如何通信方式的一次重大重写。它完全用 C++ 编写,解锁了许多新功能

  • 从原生运行时同步访问和从原生运行时同步访问
  • JavaScript 和原生代码之间的类型安全
  • 跨平台代码共享
  • 默认情况下延迟加载模块

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

在旧架构中,为了处理来自原生函数调用的响应,您需要提供一个回调,并且返回的值需要是可序列化的

// ❌ Sync callback from Native Module
nativeModule.getValue(value => {
// ❌ value cannot reference a native object
nativeModule.doSomething(value);
});

在新架构中,您可以同步调用原生函数

// ✅ Sync response from Native Module
const value = nativeModule.getValue();

// ✅ value can be a reference to a native object
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 完全类型安全,这通过强制执行 null 安全性解决了长期存在的 NullPointerReference 问题,并且能够同步调用原生模块函数使我们能够用新的原生模块 API 替换自定义 JSI 访问。”

Marc Rousavyreact-native-mmkv 的创建者

新的渲染器

我们还完全重写了原生渲染器,增加了多项好处

  • 更新可以在不同线程上以不同的优先级渲染。
  • 布局可以同步和跨不同线程读取。
  • 渲染器用 C++ 编写,并在所有平台上共享。

更新后的原生渲染器现在将视图层级结构存储在不可变的树结构中。这意味着 UI 的存储方式无法直接更改,从而可以对更新进行线程安全处理。这使其能够处理多个正在进行中的树,每个树代表用户界面的不同版本。因此,更新可以在后台渲染而不会阻塞 UI(例如在转换期间)或在主线程上渲染(以响应用户输入)。

通过支持多线程,React 可以中断低优先级更新以渲染紧急更新(例如用户输入生成的更新),然后在需要时恢复低优先级更新。新的渲染器还可以同步和跨不同线程读取布局信息。这使得可以对低优先级更新进行后台计算,并在需要时进行同步读取,例如重新定位工具提示。

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

这是朝着我们的 多平台愿景 迈出的重要一步。例如,视图扁平化是 Android 独有的优化,旨在避免深层布局树。新的渲染器具有共享的 C++ 核心,将此功能引入 iOS。此优化是自动的,不需要设置,它随共享渲染器免费提供。

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

诸如 Reanimated 等流行的库已经开始利用新的渲染器

“目前正在开发的 Reanimated 4 引入了一个新的动画引擎,该引擎直接与新的渲染器一起工作,从而允许它处理动画并跨不同线程管理布局。新的渲染器的设计是真正使这些功能得以构建的关键,而无需依赖大量变通方法。此外,由于它是用 C++ 实现并在平台之间共享的,因此 Reanimated 的大部分可以编写一次,从而减少特定于平台的问题,最大限度地减少代码库,并简化树外平台的采用。”

Krzysztof MagieraReanimated 的创建者

事件循环

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

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

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

  • 能够中断渲染以处理事件和任务
  • 更符合 Web 规范
  • 更多浏览器功能的基础

借助事件循环,React 能够可预测地对更新和事件进行排序。这允许 React 使用紧急用户事件中断低优先级更新,并且新的渲染器允许我们独立渲染这些更新。

事件循环还将事件和任务(如计时器)的行为与 Web 规范对齐,这意味着 React Native 的工作方式更像用户在 Web 中熟悉的方式,并允许 React DOM 和 React Native 之间更好地共享代码。

它还允许实现更兼容的浏览器功能,如微任务、MutationObserverIntersectionObserver。这些功能尚未准备好在 React Native 中使用,但我们正在努力在未来将它们带给您。

最后,事件循环和新的渲染器更改以支持同步读取布局,这允许 React Native 为 useLayoutEffect 添加适当的支持,以同步读取布局信息并在同一帧中更新 UI。这使您可以在元素显示给用户之前正确地定位它们。

有关更多详细信息,请参阅 useLayoutEffect

移除桥接器

在新架构中,我们还完全移除了 React Native 对桥接器的依赖,并使用 JSI 将其替换为 JavaScript 和原生代码之间的直接、高效通信

移除桥接器通过避免桥接器初始化来缩短启动时间。例如,在旧架构中,为了向 JavaScript 提供全局方法,我们需要在启动时在 JavaScript 中初始化一个模块,从而导致应用程序启动时间略有延迟

// ❌ Slow initialization
import {NativeTimingModule} from 'NativeTimingModule';
global.setTimeout = timer => {
NativeTimingModule.setTimeout(timer);
};

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

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

// ✅ Initialize directly in C++
runtime.global().setProperty(runtime, "setTimeout", createTimer);
// App.js
setTimeout(() => {}, 100);

重写还改进了错误报告,特别是对于启动时 JavaScript 崩溃的错误报告,并减少了由未定义行为引起的崩溃。如果发生崩溃,新的 React Native DevTools 可简化调试并支持新架构。

桥接器仍然保留用于向后兼容性,以支持向新架构的逐步迁移。在未来,我们将完全移除桥接器代码。

逐步迁移

我们预计大多数应用程序都可以像升级任何其他版本一样轻松地升级到 0.76。

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

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

要使用并发功能,应用程序还需要更新以支持 并发 React,方法是遵循 React 规则。要将您的 JavaScript 代码迁移到 React 18 及其语义,请遵循 React 18 升级指南

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

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

有关升级的更多详细信息,请参阅下面的 如何升级

新功能

新架构包括对 React Native 中的 React 18、并发功能和 useLayoutEffect 的完全支持。有关 React 18 功能的完整列表,请参阅 React 18 博客文章

Transitions

Transitions 是 React 18 中的一个新概念,用于区分紧急更新和非紧急更新。

  • 紧急更新 反映直接交互,例如键入和按下。
  • Transition 更新 将 UI 从一个视图过渡到另一个视图。

紧急更新需要立即响应,以符合我们对物理对象行为方式的直觉。但是,转换是不同的,因为用户不希望在屏幕上看到每个中间值。在新架构中,React Native 能够支持分别渲染紧急更新和 transition 更新。

通常,为了获得最佳用户体验,单个用户输入应同时导致紧急更新和非紧急更新。与 ReactDOM 类似,presschange 等事件被视为紧急事件并立即渲染。您可以使用输入事件内的 startTransition API 来通知 React 哪些更新是“transition”并且可以推迟到后台

import {startTransition} from 'react';

// Urgent: Show the slider value
setCount(input);

// Mark any state updates inside as transitions
startTransition(() => {
// Transition: Show the results
setNumberOfTiles(input);
});

将紧急事件与 transition 分开可以实现更灵敏的用户界面和更直观的用户体验。

这是旧架构(没有 transition)和新架构(带有 transition)的比较。想象一下,每个图块都不是带有背景颜色的简单视图,而是一个包含图像和其他难以渲染的组件的丰富组件。使用 useTransition 后,您可以避免使用更新来折腾您的应用程序并落后。

A video demonstrating an app rendering many views (tiles) according to a slider input. The views are rendered in batches as the slider is quickly adjusted from 0 to 1000.
之前: 渲染图块而不将其标记为 transition。
A video demonstrating an app rendering many views (tiles) according to a slider input. The views are rendered in batches as the slider is quickly adjusted from 0 to 1000. There are less batch renders in comparison to the next video.
之后: 使用 transition 渲染图块以中断陈旧状态的正在进行的渲染。

有关更多信息,请参阅 对并发渲染器和功能的支持

自动批处理

升级到新架构后,您将受益于 React 18 的自动批处理。

自动批处理允许 React 在渲染时将更多状态更新批处理在一起,以避免渲染中间状态。这使得 React Native 速度更快,并且不易受到延迟的影响,而无需开发人员编写任何额外的代码。

A video demonstrating an app rendering many views according to a slider input. The slider value is adjusted from 0 to 1000 and the UI slowly catches up to rendering 1000 views.
之前: 使用旧版渲染器渲染频繁的状态更新。
A video demonstrating an app rendering many views according to a slider input. The slider value is adjusted from 0 to 1000 and the UI resolves to 1000 views faster than the previous example, without as many intermediate states.
之后: 使用自动批处理渲染频繁的状态更新。

在旧架构中,渲染了更多中间状态,并且即使滑块停止移动,UI 也会不断更新。由于自动批处理更新,新架构渲染的中间状态更少,并且完成渲染的速度更快。

有关更多信息,请参阅 对并发渲染器和功能的支持

useLayoutEffect

基于事件循环和同步读取布局的能力,我们在新架构中为 React Native 添加了对 useLayoutEffect 的适当支持。

在旧架构中,您需要使用异步 onLayout 事件来读取视图的布局信息(这也是异步的)。因此,至少会有一帧布局不正确,直到读取和更新布局,从而导致工具提示放置在错误位置等问题

// ❌ async onLayout after commit
const onLayout = React.useCallback(event => {
// ❌ async callback to read layout
ref.current?.measureInWindow((x, y, width, height) => {
setPosition({x, y, width, height});
});
}, []);

// ...
<ViewWithTooltip
onLayout={onLayout}
ref={ref}
position={position}
/>;

新架构通过允许在 useLayoutEffect 中同步访问布局信息来解决此问题

// ✅ sync layout effect during commit
useLayoutEffect(() => {
// ✅ sync call to read layout
const rect = ref.current?.getBoundingClientRect();
setPosition(rect);
}, []);

// ...
<ViewWithTooltip ref={ref} position={position} />;

此更改允许您同步读取布局信息并在同一帧中更新 UI,从而使您可以在元素显示给用户之前正确地定位它们

A view that is moving to the corners of the viewport and center with a tooltip rendered either above or below it. The tooltip is rendered after a short delay after the view moves
在旧架构中,布局在 onLayout 中异步读取,导致工具提示的位置延迟。
A view that is moving to the corners of the viewport and center with a tooltip rendered either above or below it. The view and tooltip move in unison.
在新架构中,可以在 useLayoutEffect 中同步读取布局,从而在显示之前更新工具提示位置。

有关更多信息,请参阅 同步布局和效果 的文档。

完全支持 Suspense

Suspense 允许您声明式地指定组件树的一部分的加载状态(如果它尚未准备好显示)

<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>

我们在几年前引入了 Suspense 的有限版本,React 18 添加了完全支持。到目前为止,React Native 还无法支持 Suspense 的并发渲染。

新架构包括对 React 18 中引入的 Suspense 的完全支持。这意味着您现在可以在 React Native 中使用 Suspense 来处理组件的加载状态,并且挂起的内容将在后台渲染,同时显示加载状态,从而为可见内容上的用户输入提供更高的优先级。

有关更多信息,请参阅 React 18 中 Suspense 的 RFC

如何升级

要升级到 0.76,请按照 发布帖子 中的步骤操作。由于此版本也升级到 React 18,因此您还需要遵循 React 18 升级指南

由于与旧架构的互操作层,这些步骤对于大多数应用程序来说应该足以升级到新架构。但是,要充分利用新架构并开始使用并发功能,您需要迁移自定义原生模块和原生组件以支持新的原生模块和原生组件 API。

如果不迁移您的自定义原生模块,您将无法获得共享 C++、同步方法调用或来自 codegen 的类型安全的好处。如果不迁移您的原生组件,您将无法使用并发功能。我们建议尽快将所有原生组件和原生模块迁移到新架构。

注意

在未来的版本中,我们将移除互操作层,模块将需要支持新架构。

应用

如果您是应用程序开发人员,要完全支持新架构,您将需要升级您的库、自定义原生组件和自定义原生模块,以完全支持新架构。

我们已与最流行的 React Native 库合作,以确保对新架构的支持。您可以在 reactnative.directory 网站上查看库与新架构的兼容性。

如果您的应用程序依赖的任何库尚不兼容,您可以

  • 向库提出问题,并要求作者迁移到新架构。
  • 如果库未维护,请考虑具有相同功能的替代库。
  • 选择退出新架构,同时迁移这些库。

如果您的应用程序具有自定义原生模块或自定义原生组件,我们希望它们能够正常工作,这要归功于我们的 互操作层。但是,我们建议将它们升级到新的原生模块和原生组件 API,以完全支持新架构并采用并发功能。

请按照这些指南将您的模块和组件迁移到新架构

如果您是库维护者,请首先测试您的库是否与互操作层一起工作。如果不起作用,请在新架构工作组的 GitHub 存储库上提出问题。

为了完全支持新架构,我们建议尽快将您的库迁移到新的原生模块和原生组件 API。这将允许您的库的用户充分利用新架构并支持并发功能。

您可以按照这些指南将您的模块和组件迁移到新架构

选择退出

如果由于任何原因,新架构在您的应用程序中运行不正常,您可以随时选择退出,直到您准备好再次启用它。

退出新架构

  • 在 Android 上,修改 android/gradle.properties 文件并关闭 newArchEnabled 标志
-newArchEnabled=true
+newArchEnabled=false
  • 在 iOS 上,您可以通过运行以下命令重新安装依赖项
RCT_NEW_ARCH_ENABLED=0 bundle exec pod install

感谢

将新架构交付给 OSS 社区是一项巨大的努力,花费了我们数年的研究和开发。我们想借此机会感谢所有现任和前任 React 团队成员,他们帮助我们取得了这一成果。

我们也非常感谢所有与我们合作促成此事的合作伙伴。 特别是,我们要特别提及:

  • Expo,感谢他们早期采用新架构,并支持迁移最流行的库的工作。
  • Software Mansion,感谢他们维护生态系统中至关重要的库,早期将它们迁移到新架构,以及在调查和修复各种问题方面提供的所有帮助。
  • Callstack,感谢他们维护生态系统中至关重要的库,早期将它们迁移到新架构,以及在 Community CLI 工作上提供的支持。
  • Microsoft,感谢他们为 react-native-windowsreact-native-macos 以及其他几个开发者工具添加了新架构的实现。
  • ExpensifyKrakenBlueskyBrigad,感谢他们率先采用新架构并报告各种问题,以便我们能够为所有人修复这些问题。
  • 感谢所有独立的库维护者和开发者,他们通过测试新架构、修复一些问题以及就模糊不清的事项提出问题来为新架构做出贡献,以便我们能够澄清这些问题。