关于新架构
自 2018 年以来,React Native 团队一直在重新设计 React Native 的核心内部结构,以使开发人员能够创建更高质量的体验。截至 2024 年,这个版本的 React Native 已经在大规模实践中得到验证,并为 Meta 的生产应用提供支持。
“新架构”一词指的是新的框架架构以及将其开源的工作。
自 React Native 0.68 版本起,新架构已作为实验性选项提供,并在随后的每个版本中不断改进。团队目前正致力于使其成为 React Native 开源生态系统的默认体验。
为什么需要新架构?
在多年的 React Native 构建经验之后,团队发现了一系列限制,这些限制阻止开发人员使用高精细度来制作某些体验。这些限制是框架现有设计的基础,因此新架构最初是对 React Native 未来的投资。
新架构解锁了在旧架构中不可能实现的功能和改进。
同步布局和效果
构建自适应 UI 体验通常需要测量视图的大小和位置,并调整布局。
如今,您可以使用 onLayout
事件来获取视图的布局信息并进行任何调整。但是,onLayout
回调中的状态更新可能会在绘制上一次渲染之后应用。这意味着用户可能会在渲染初始布局和响应布局测量之间看到中间状态或视觉跳跃。
借助新架构,我们可以通过同步访问布局信息和正确调度的更新来完全避免此问题,从而确保用户看不到任何中间状态。
示例:渲染工具提示
测量工具提示并将其放置在视图上方使我们能够展示同步渲染所解锁的功能。工具提示需要知道其目标视图的位置以确定其应渲染的位置。
在当前的架构中,我们使用 onLayout
获取视图的测量值,然后根据视图的位置更新工具提示的位置。
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
在单个提交中同步测量和应用布局更新,从而避免视觉“跳跃”。
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} />
</>
);
}
支持并发渲染器和功能
新架构支持并发渲染和 React 18 及更高版本中发布的功能。您现在可以在 React Native 代码中使用诸如 Suspense for data-fetching、Transitions 和其他新的 React API 等功能,从而进一步统一 Web 和原生 React 开发之间的代码库和概念。
并发渲染器还带来了开箱即用的改进,例如自动批处理,这减少了 React 中的重新渲染。
示例:自动批处理
借助新架构,您将通过 React 18 渲染器获得自动批处理。
在此示例中,滑块指定要渲染的图块数量。将滑块从 0 拖动到 1000 将触发一系列快速的状态更新和重新渲染。
在比较 相同代码的渲染器时,您可以直观地注意到该渲染器提供了更流畅的 UI,以及更少的中间 UI 更新。来自原生事件处理程序(例如此原生 Slider 组件)的状态更新现在已进行批处理。


新的并发功能,例如 Transitions,使您能够表达 UI 更新的优先级。将更新标记为较低优先级会告诉 React 它可以“中断”渲染更新以处理更高优先级的更新,从而确保在重要的地方获得响应灵敏的用户体验。
示例:使用 startTransition
我们可以基于之前的示例来展示 transitions 如何中断正在进行的渲染以处理较新的状态更新。
我们将图块数量状态更新包装在 startTransition
中,以指示可以中断图块的渲染。startTransition
还提供了一个 isPending
标志,以告知我们 transition 何时完成。
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>
)
}
您会注意到,在 transition 中频繁更新的情况下,React 渲染的中间状态更少,因为它会在状态变为陈旧时立即停止渲染该状态。相比之下,在没有 transition 的情况下,会渲染更多的中间状态。这两个示例仍然使用自动批处理。尽管如此,transitions 仍为开发人员提供了更大的能力来批处理正在进行的渲染。
快速 JavaScript/原生接口
新架构移除了 JavaScript 和原生之间的异步桥,并用 JavaScript 接口 (JSI) 替代。JSI 是一个接口,允许 JavaScript 持有对 C++ 对象的引用,反之亦然。通过内存引用,您可以直接调用方法,而无需序列化成本。
JSI 使 VisionCamera(一个流行的 React Native 相机库)能够实时处理帧。典型的帧缓冲区为 10 MB,这意味着大约每秒 1 GB 的数据,具体取决于帧速率。与桥的序列化成本相比,JSI 可以轻松处理如此大量的数据接口。JSI 可以公开其他基于实例的复杂类型,例如数据库、图像、音频样本等。
在新架构中采用 JSI 消除了所有原生-JavaScript 互操作中的此类序列化工作。这包括初始化和重新渲染原生核心组件,例如 View
和 Text
。您可以阅读更多关于我们在新架构中 渲染性能方面的调查 以及我们测得的改进基准。
启用新架构后,我能期待什么?
虽然新架构启用了这些功能和改进,但为您的应用或库启用新架构可能不会立即提高性能或用户体验。
例如,您的代码可能需要重构才能利用新的功能,例如同步布局效果或并发功能。尽管 JSI 将最大限度地减少 JavaScript 和原生内存之间的开销,但数据序列化可能不是您应用性能的瓶颈。
在您的应用或库中启用新架构意味着选择加入 React Native 的未来。
团队正在积极研究和开发新架构解锁的新功能。例如,Web 对齐是 Meta 正在积极探索的领域,它将发布到 React Native 开源生态系统。
您可以在我们专门的 讨论和提案 存储库中关注并做出贡献。
我今天应该使用新架构吗?
在 0.76 版本中,新架构在所有 React Native 项目中默认启用。
如果您发现任何工作不正常的地方,请使用此模板打开一个 issue。
如果由于任何原因您无法使用新架构,您仍然可以选择退出
Android
- 打开
android/gradle.properties
文件 - 将
newArchEnabled
标志从true
切换到false
# 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
- 打开
ios/Podfile
文件 - 在 Podfile 的主作用域中添加
ENV['RCT_NEW_ARCH_ENABLED'] = '0'
(模板中的参考 Podfile)
+ 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(
- 使用以下命令安装您的 CocoaPods 依赖项
bundle exec pod install