跳到主要内容

关于新架构

自 2018 年以来,React Native 团队一直在重新设计 React Native 的核心内部结构,旨在让开发者能够创造更高质量的体验。截至 2024 年,该版本的 React Native 已在大规模应用中得到验证,并为 Meta 的生产环境应用程序提供支持。

术语“新架构”既指代新的框架架构,也指代将其引入开源社区所做的工作。

React Native 0.68 起,新架构已可供实验性选择使用,并在随后的每个版本中持续改进。目前,团队正致力于将其打造为 React Native 开源生态系统的默认体验。

为什么要采用新架构?

在使用 React Native 构建应用多年后,团队发现了一系列限制,这些限制阻碍了开发者打造某些精美体验。这些限制是原有框架设计中根深蒂固的问题,因此,新架构的研发最初是作为对 React Native 未来的一项投资。

新架构解锁了旧架构中无法实现的功能和改进。

同步布局与副作用

构建自适应 UI 体验通常需要测量视图的大小和位置,并据此调整布局。

目前,你会使用 onLayout 事件来获取视图的布局信息并进行调整。然而,onLayout 回调中的状态更新可能会在上次渲染绘制之后才应用。这意味着用户可能会在初始布局渲染和响应布局测量之间看到中间状态或视觉上的跳动。

通过新架构,我们可以利用对布局信息的同步访问以及妥善调度的更新,从而完全避免这个问题,确保用户不会看到任何中间状态。

示例:渲染工具提示(Tooltip)

在视图上方测量并放置工具提示,可以展示同步渲染所带来的能力。工具提示需要知道其目标视图的位置,以确定渲染位置。

在当前架构中,我们使用 onLayout 获取视图的测量值,然后根据视图所在的位置更新工具提示的定位。

React JSX
function ViewWithTooltip() {
// ...

// We get the layout information and pass to Tooltip to position itself
const onLayout = React.useCallback(event => {
targetRef.current?.measureInWindow((x, y, width, height) => {
// This state update is not guaranteed to run in the same commit
// This results in a visual "jump" as the Tooltip repositions itself
setTargetRect({x, y, width, height});
});
}, []);

return (
<>
<View ref={targetRef} onLayout={onLayout}>
<Text>Some content that renders a tooltip above</Text>
</View>
<Tooltip targetRect={targetRect} />
</>
);
}

利用新架构,我们可以使用 useLayoutEffect 在单次提交中同步测量并应用布局更新,从而避免视觉上的“跳动”。

React JSX
function ViewWithTooltip() {
// ...

useLayoutEffect(() => {
// The measurement and state update for `targetRect` happens in a single commit
// allowing Tooltip to position itself without intermediate paints
targetRef.current?.measureInWindow((x, y, width, height) => {
setTargetRect({x, y, width, height});
});
}, [setTargetRect]);

return (
<>
<View ref={targetRef}>
<Text>Some content that renders a tooltip above</Text>
</View>
<Tooltip targetRect={targetRect} />
</>
);
}
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
工具提示的异步测量与渲染。查看代码
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.
工具提示的同步测量与渲染。查看代码

支持并发渲染器与相关特性

新架构支持在 React 18 及更高版本中推出的并发渲染和特性。现在,你可以在 React Native 代码中使用诸如 Suspense 数据获取、Transitions 以及其他新的 React API,进一步统一了 Web 和原生 React 开发的代码库与概念。

并发渲染器还带来了开箱即用的改进,例如自动批处理,它减少了 React 中的重新渲染次数。

示例:自动批处理

通过新架构,你可以利用 React 18 渲染器获得自动批处理功能。

在此示例中,滑块指定了要渲染的瓦片数量。将滑块从 0 拖动到 1000 会触发一连串快速的状态更新和重新渲染。

在对比同一代码的两种渲染器时,你可以直观地观察到,该渲染器提供了更流畅的 UI,中间 UI 更新更少。来自原生事件处理器(如本例中的原生 Slider 组件)的状态更新现在已实现批处理。

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.
使用 React 18 渲染器渲染频繁的状态更新。

新的并发特性(如 Transitions)让你有能力表达 UI 更新的优先级。将更新标记为低优先级会告诉 React,它可以“中断”该更新的渲染,以处理更高优先级的更新,从而确保在关键位置提供响应迅速的用户体验。

示例:使用 startTransition

我们可以基于之前的示例来展示转换(transitions)如何中断正在进行的渲染,以处理较新的状态更新。

我们使用 startTransition 包装瓦片数量的状态更新,以表明渲染瓦片的过程可以被中断。startTransition 还提供了一个 isPending 标志,告诉我们转换何时完成。

React JSX
function TileSlider({value, onValueChange}) {
const [isPending, startTransition] = useTransition();

return (
<>
<View>
<Text>
Render {value} Tiles
</Text>
<ActivityIndicator animating={isPending} />
</View>
<Slider
value={1}
minimumValue={1}
maximumValue={1000}
step={1}
onValueChange={newValue => {
startTransition(() => {
onValueChange(newValue);
});
}}
/>
</>
);
}

function ManyTiles() {
const [value, setValue] = useState(1);
const tiles = generateTileViews(value);
return (
<TileSlider onValueChange={setValue} value={value} />
<View>
{tiles}
</View>
)
}

你会注意到,在转换中进行频繁更新时,React 渲染的中间状态更少,因为它一旦发现状态过期就会放弃渲染。相比之下,如果不使用转换,则会渲染更多的中间状态。两个示例都使用了自动批处理,但转换赋予了开发者在处理进行中渲染时更强大的批处理能力。

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.
使用转换渲染瓦片,中断已过期状态的渲染过程。查看代码
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.
不标记为转换的情况下渲染瓦片。查看代码

快速的 JavaScript/原生互操作

新架构移除了 JavaScript 与原生之间异步桥接(asynchronous bridge),并以 JavaScript Interface (JSI) 取代。JSI 是一个允许 JavaScript 持有 C++ 对象引用(反之亦然)的接口。有了内存引用,你无需序列化成本即可直接调用方法。

JSI 使流行的 React Native 相机库 VisionCamera 能够实时处理帧数据。典型的帧缓冲区约为 30 MB,根据帧率不同,每秒产生约 2 GB 的数据。与桥接器的序列化成本相比,JSI 可以轻松处理此类接口数据。JSI 还可以暴露其他复杂的基于实例的类型,如数据库、图像、音频采样等。

新架构中采用 JSI 移除了所有原生与 JavaScript 互操作中这一类序列化工作。这包括初始化和重新渲染核心原生组件(如 ViewText)。你可以阅读更多关于我们在新架构中针对渲染性能的调研以及我们测得的性能改进指标。

启用新架构后我能期待什么?

虽然新架构支持了这些特性和改进,但在你的应用或库中启用新架构可能不会立即提高性能或用户体验。

例如,你的代码可能需要重构才能利用同步布局效果或并发特性等新能力。尽管 JSI 会最小化 JavaScript 和原生内存之间的开销,但数据序列化此前未必是你应用的性能瓶颈。

在你的应用或库中启用新架构,意味着你选择了拥抱 React Native 的未来。

团队正在积极研究和开发新架构解锁的各项新功能。例如,Web 对齐(Web alignment)是 Meta 正在进行的一项探索领域,未来将发布到 React Native 开源生态系统中。

你可以在我们专门的讨论与提案仓库中关注并参与贡献。

我现在应该使用新架构吗?

从 0.76 版本开始,所有 React Native 项目默认启用新架构。

如果你发现任何运行不正常的问题,请使用此模板提交问题。

如果由于任何原因你无法使用新架构,你仍然可以选择将其关闭:

Android

  1. 打开 android/gradle.properties 文件
  2. newArchEnabled 标志从 true 改为 false
gradle.properties
# Use this property to enable support to the new architecture.
# This will allow you to use TurboModules and the Fabric render in
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
-newArchEnabled=true
+newArchEnabled=false

iOS

  1. 打开 ios/Podfile 文件
  2. 在 Podfile 的主作用域中添加 ENV['RCT_NEW_ARCH_ENABLED'] = '0'(参考模板中的 Podfile
差异 (Diff)
+ ENV['RCT_NEW_ARCH_ENABLED'] = '0'
# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
  1. 使用以下命令安装 CocoaPods 依赖
bundle exec pod install