SDK 55 中的 Expo UI:React Native 应用现在可以用上 Jetpack Compose
SDK 55 中的 Expo UI 将 Jetpack Compose 带入 beta 阶段,并让 SwiftUI API 与 Apple 的惯例保持一致,你掌握的原生框架知识可以直接迁移过来。
中文
复制

Expo UI 把 SwiftUI 和 Jetpack Compose 直接带进了 React Native 应用。它没有用 JavaScript 重新实现原生组件,而是把真正的那一套暴露出来——原生开发者所用的组件、行为和平台打磨,完全一致。
在 SDK 54 中,我们推出了 Expo UI,支持 SwiftUI,并为 Jetpack Compose 提供了早期 alpha 版本。此后,开发者一直在用它构建应用——我们还把 Expo UI 作为 expo-widgets 的基础,这验证了它在真实生产环境中的可行性。但 Jetpack Compose 支持仍处于 alpha 阶段,我们还没有用完整的应用对它做过测试。
SDK 55 同时解决了这两个问题。Jetpack Compose 现已进入 beta,提供了足以构建完整应用的 Material Design 3 组件,SwiftUI API 也经过重构,与 Apple 的设计高度对齐;因此你对这两个框架的已有知识可以直接迁移过来。
为什么这很重要
Apple 和 Google 持续在 SwiftUI 和 Jetpack Compose 上投入大量资源,许多最新的 API 和组件都率先出现在这两个框架中。用 JavaScript 重新实现它们工作量巨大,而且很难跟上节奏。
Expo UI 走的是另一条路:直接暴露原生框架。平台一发布新功能你就能用上,而且由于 API 表面遵循 SwiftUI 和 Jetpack Compose 的惯例,任何已经熟悉这些框架的开发者(或 AI)都能直接编写 Expo UI 代码,无需再学新东西。
Jetpack Compose:从 alpha 到 beta
为了测试 Compose API 是否已准备好投入实际使用,我们只用 Expo UI 组件构建了一个完整的应用 WikiReader(@nsh07 的原版的克隆)。我们想弄清楚缺了什么,以及哪些地方还不够完善。

这个应用全程使用 Material Design 3 组件——Card、LazyColumn、ListItem、DockedSearchBar、ModalBottomSheet 等等。我们按应用的实际需要逐个添加组件。每个组件都必须解决应用中的真实问题,才能被纳入进来。
下面是 Expo UI 配合 Jetpack Compose 时设置界面的样子:
import { useState } from "react";
import { useColorScheme } from "react-native";
import { Host, Switch, Icon, ListItem, LazyColumn } from "@expo/ui/jetpack-compose";
import { clip, Shapes } from "@expo/ui/jetpack-compose/modifiers";
export default function SettingsScreen() {
const colorScheme = useColorScheme() ?? "light";
const [wifi, setWifi] = useState(true);
const [bluetooth, setBluetooth] = useState(false);
const [darkMode, setDarkMode] = useState(false);
return (
<Host style={{ flex: 1 }} colorScheme={colorScheme}>
<LazyColumn
verticalArrangement={{ spacedBy: 2 }}
contentPadding={{ start: 16, end: 16, top: 8, bottom: 16 }}
>
<ListItem headline="Wi-Fi" supportingText="Connected" modifiers={[clip(Shapes.RoundedCorner({ topStart: 12, topEnd: 12 }))]}>
<ListItem.Leading>
<Icon source={require("../../assets/icons/wifi.xml")} tintColor="#1d1b20" />
</ListItem.Leading>
<ListItem.Trailing>
<Switch value={wifi} onValueChange={setWifi} />
</ListItem.Trailing>
</ListItem>
<ListItem headline="Bluetooth" supportingText="Off">
<ListItem.Leading>
<Icon source={require("../../assets/icons/bluetooth.xml")} tintColor="#1d1b20" />
</ListItem.Leading>
<ListItem.Trailing>
<Switch value={bluetooth} onValueChange={setBluetooth} />
</ListItem.Trailing>
</ListItem>
<ListItem headline="Dark mode" modifiers={[clip(Shapes.RoundedCorner({ bottomStart: 12, bottomEnd: 12 }))]}>
<ListItem.Leading>
<Icon source={require("../../assets/icons/dark_mode.xml")} tintColor="#1d1b20" />
</ListItem.Leading>
<ListItem.Trailing>
<Switch value={darkMode} onValueChange={setDarkMode} />
</ListItem.Trailing>
</ListItem>
</LazyColumn>
</Host>
);
}
如果你用过 Jetpack Compose,这些组件名和结构应该很眼熟——LazyColumn 带 arrangement 和 padding,modifier 链,ListItem 带 leading 和 trailing 内容。在 Compose 里,这些是作为 composable lambda 参数传进去的。在 Expo UI 里,我们用 React 的复合组件模式(ListItem.Leading、ListItem.Trailing)在 JSX 中表达同样的布局。
modifier 系统也对齐了。Jetpack Compose 有自己的 modifier 链,我们把它搬到了 JSX 里,风格和 @expo/ui 为 SwiftUI 所做的一致,这样 modifier 在两个平台上的行为就统一了:
<Box modifiers={[size(100, 100), background(Color.android.dynamic.primaryContainer)]}>
<Text modifiers={[padding(16)]}>Hello from Compose</Text>
</Box>
这包括作用域 modifier:Row 和 Column 各自有专属选项,比如 weight 和 matchParentSize,和 Compose 里一样。进一步了解 modifier。
我们还加了一个 Icon 组件,原生渲染 Material Symbols 的 XML vector drawable——在 Jetpack Compose 开发中,把 Material Symbols 当作 XML drawable 用是常见做法。你从 Material Symbols 下载图标,得到 Android drawable XML 文件,直接引用即可:
<Icon source={require('./path/to/symbol.xml')} />
如果一个一个下载 XML 文件嫌麻烦,可以让你的 AI agent 帮你生成。在我们的场景里这招很好用——AI 把 Expo UI 代码和图标 XML 文件一起写了出来。
我们没打算把每个 Material Design 3 组件都包一遍。WikiReader 复刻版需要什么,我们就加什么,之后会根据需求继续扩展。感谢所有帮忙添加这些组件的外部贡献者。
从 @expo/ui/jetpack-compose 文档中了解更多。
SwiftUI:现在它真的像 SwiftUI 了
在 SDK 55 中,我们重命名并重构了 SwiftUI 组件,让它们与 SwiftUI 中对应的组件保持一致。DateTimePicker 现在是 DatePicker,Switch 现在是 Toggle,CircularProgress 现在是 ProgressView。如果你用过 SwiftUI,组件名、modifier 名和整体结构应该都很熟悉。在 Apple 文档里查到的内容,可以直接对应到 Expo UI;你可以直接照着 Apple 的文档写,不用再学一层翻译,AI 工具也能凭已有的训练数据生成正确的代码。
由于我们无法覆盖所有 SwiftUI 视图和修饰符,我们也让 Expo UI 具备了可扩展性。对于尚未覆盖的部分,你可以创建自己的自定义 SwiftUI 视图和修饰符。
hot-chocolate 示例应用已更新至 SDK 55 和最新 API。想了解在 Expo 中使用 SwiftUI 构建的完整示例,可以看看它。
用 AI 构建
与原生框架保持一致的一个实际好处,是它与 AI 配合得非常好。
由于 Expo UI 遵循 SwiftUI 和 Jetpack Compose 的约定,AI 工具已经对这些模式有深入的了解。我们顺势在 expo/skills 中添加了技能,让 AI 助手掌握 Expo UI 的具体细节,并构建了 expo-mcp 以实现更紧密的集成。
工作流程是:上传一张截图,或者描述你想要的效果,AI 就会用它已经熟悉的模式,为 SwiftUI 和 Jetpack Compose 生成 Expo UI 代码。现在就可以试试这个提示词:
使用 @expo/ui/jetpack-compose 组件(Column、Row、Switch、Slider)构建一个 Android 设置风格的界面,行图标使用 Material Symbols,包含分组区块(网络、显示、声音、隐私),配有开关和亮度滑块。
下一步
我们正在推进 Expo UI 中的通用组件:一套可在 SwiftUI 和 Jetpack Compose(未来也可能在 web 上)通用的共享组件 API。
但现在,我们非常希望听到你对 SDK 55 的反馈。试着用 Expo UI 构建一个应用——无论是 @expo/ui/swift-ui 还是 @expo/ui/jetpack-compose 那一侧——然后告诉我们体验如何。还缺哪些组件?哪里感觉不对劲? 你的意见会直接影响我们接下来的开发方向。