🚀 快速安装
复制以下命令并运行,立即安装此 Skill:
npx @anthropic-ai/skills install expo/skills/building-native-ui
💡 提示:需要 Node.js 和 NPM
Expo UI 指南
参考资料
根据需要查阅以下资源:
references/
animations.md 重新动画化:入场、出场、布局、滚动驱动、手势
controls.md 原生 iOS:开关、滑块、分段控件、日期时间选择器、选择器
form-sheet.md expo-router 中的表单:配置、底部操作栏和背景交互。
gradients.md 通过 experimental_backgroundImage 实现 CSS 渐变(仅限新架构)
icons.md 通过 expo-image(sf: 源)实现 SF 符号,名称、动画、字重
media.md 相机、音频、视频和文件保存
route-structure.md 路由约定、动态路由、组、文件夹组织
search.md 带标题的搜索栏、useSearch 钩子、过滤模式
storage.md SQLite, AsyncStorage, SecureStore
tabs.md 原生标签页、从 JS 标签页迁移、iOS 26 新特性
toolbar-and-headers.md 堆栈标题和工具栏按钮、菜单、搜索(仅限 iOS)
visual-effects.md 模糊 (expo-blur) 和液态玻璃 (expo-glass-effect)
webgpu-three.md 使用 WebGPU 和 Three.js 实现 3D 图形、游戏、GPU 可视化
zoom-transitions.md 苹果缩放:使用 Link.AppleZoom 实现流畅的缩放过渡 (iOS 18+)
运行应用
关键:在创建自定义构建之前,始终优先尝试 Expo Go。
大多数 Expo 应用无需任何自定义原生代码即可在 Expo Go 中运行。在运行 npx expo run:ios 或 npx expo run:android 之前:
- 从 Expo Go 开始:运行
npx expo start并使用 Expo Go 扫描二维码 - 检查功能是否正常:在 Expo Go 中全面测试你的应用
- 仅在必要时创建自定义构建 – 请参阅下方说明
何时需要自定义构建
仅在以下情况需要使用 npx expo run:ios/android 或 eas build:
- 本地 Expo 模块(
modules/中的自定义原生代码) - Apple 目标(通过
@bacons/apple-targets实现的小组件、应用剪辑、扩展) - Expo Go 未包含的第三方原生模块
- 无法在
app.json中表达的自定义原生配置
Expo Go 何时适用
Expo Go 开箱即支持大量功能:
- 所有
expo-*包(相机、位置、通知等) - Expo Router 导航
- 大多数 UI 库(reanimated, gesture handler 等)
- 推送通知、深度链接等
如果不确定,请先尝试 Expo Go。创建自定义构建会增加复杂性,导致迭代变慢,并需要设置 Xcode/Android Studio。
代码风格
- 注意未终止的字符串。确保嵌套的反引号被转义;永远不要忘记正确转义引号。
- 始终在文件顶部使用 import 语句。
- 文件命名始终使用短横线命名法,例如
comment-card.tsx - 移动或重构导航时,始终删除旧的路由文件
- 文件命名中切勿使用特殊字符
- 配置 tsconfig.json 使用路径别名,并且为了便于重构,优先使用别名而非相对导入。
路由
有关详细的路由约定,请参阅 ./references/route-structure.md。
- 路由属于
app目录。 - 切勿将组件、类型或工具程序与路由文件放在同一目录(app 目录下)。这是一种反模式。
- 确保应用始终有一个匹配 “/” 的路由,它可能位于某个路由组内部。
库偏好
- 切勿使用已从 React Native 中移除的模块,如 Picker, WebView, SafeAreaView 或 AsyncStorage
- 切勿使用旧版 expo-permissions
- 使用
expo-audio而非expo-av - 使用
expo-video而非expo-av - 对于 SF 符号,使用带有
source="sf:name"的expo-image,而非expo-symbols或@expo/vector-icons - 使用
react-native-safe-area-context而非 react-native 的 SafeAreaView - 使用
process.env.EXPO_OS而非Platform.OS - 使用
React.use而非React.useContext - 使用
expo-image的 Image 组件而非原生元素img - 使用
expo-glass-effect实现液态玻璃背景效果
响应式设计
- 始终将根组件包裹在滚动视图中以实现响应式布局
- 使用
<ScrollView contentInsetAdjustmentBehavior="automatic" />替代<SafeAreaView>以获得更智能的安全区域内边距 contentInsetAdjustmentBehavior="automatic"也应应用于 FlatList 和 SectionList- 使用 flexbox 而非 Dimensions API
- 测量屏幕大小时,始终优先使用
useWindowDimensions而非Dimensions.get()
行为
- 在 iOS 上有条件地使用 expo-haptics 以创造更愉悦的体验
- 使用内置触感的视图,如 React Native 的
<Switch />和@react-native-community/datetimepicker - 当一个路由属于某个堆栈时,其第一个子元素几乎总是应是一个设置了
contentInsetAdjustmentBehavior="automatic"的 ScrollView - 向页面添加
ScrollView时,它几乎总是应该作为路由组件内部的第一个组件 - 优先使用 Stack.Screen 选项中的
headerSearchBarOptions来添加搜索栏 - 在包含可复制数据的文本上使用
<Text selectable />属性 - 考虑格式化大数字,如 140 万 或 3.8 万
- 除非在 webview 或 Expo DOM 组件中,否则切勿使用 ‘img’ 或 ‘div’ 等原生元素
样式
遵循苹果人机界面指南。
通用样式规则
- 优先使用 flex gap 而非外边距和内边距样式
- 在可能的情况下,优先使用内边距而非外边距
- 始终考虑安全区域,通过堆栈标题、标签页或 ScrollView/FlatList 的
contentInsetAdjustmentBehavior="automatic"来处理 - 确保顶部和底部的安全区域内边距都被考虑
- 如果复用样式没有更快,使用内联样式而非 StyleSheet.create
- 为状态变化添加入场和出场动画
- 对于圆角,使用
{ borderCurve: 'continuous' },除非创建胶囊形状 - 始终使用导航堆栈标题,而不是在页面上使用自定义文本元素
- 当为 ScrollView 添加内边距时,使用
contentContainerStyle的内边距和间距,而不是直接在 ScrollView 本身上设置内边距(减少裁剪) - 不支持 CSS 和 Tailwind – 使用内联样式
文本样式
- 为每个显示重要数据或错误信息的
<Text/>元素添加selectable属性 - 计数器应使用
{ fontVariant: 'tabular-nums' }以实现数字对齐
阴影
使用 CSS boxShadow 样式属性。切勿使用旧的 React Native 阴影或 elevation 样式。
<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />
支持 ‘inset’ 阴影。
导航
链接 (Link)
使用来自 ‘expo-router’ 的 <Link href="/path" /> 在路由之间导航。
import { Link } from 'expo-router';
// 基础链接
<Link href="/path" />
// 包裹自定义组件
<Link href="/path" asChild>
<Pressable>...</Pressable>
</Link>
在可能的情况下,添加一个 <Link.Preview> 以遵循 iOS 惯例。经常添加上下文菜单和预览以增强导航体验。
堆栈 (Stack)
- 始终使用
_layout.tsx文件来定义堆栈 - 使用来自 ‘expo-router/stack’ 的 Stack 实现原生导航堆栈
页面标题
在 Stack.Screen 选项中设置页面标题:
<Stack.Screen options={{ title: "首页" }} />
上下文菜单
为链接组件添加长按上下文菜单:
import { Link } from "expo-router";
<Link href="/settings" asChild>
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Menu>
<Link.MenuAction
title="分享"
icon="square.and.arrow.up"
onPress={handleSharePress}
/>
<Link.MenuAction
title="屏蔽"
icon="nosign"
destructive
onPress={handleBlockPress}
/>
<Link.Menu title="更多" icon="ellipsis">
<Link.MenuAction title="复制" icon="doc.on.doc" onPress={() => {}} />
<Link.MenuAction
title="删除"
icon="trash"
destructive
onPress={() => {}}
/>
</Link.Menu>
</Link.Menu>
</Link>;
链接预览
经常使用链接预览来增强导航体验:
<Link href="/settings">
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Preview />
</Link>
链接预览可以与上下文菜单一起使用。
模态 (Modal)
以模态形式展示屏幕:
<Stack.Screen name="modal" options={{ presentation: "modal" }} />
优先使用这种方式,而非构建自定义模态组件。
表单
以动态表单形式展示屏幕:
<Stack.Screen
name="sheet"
options={{
presentation: "formSheet",
sheetGrabberVisible: true,
sheetAllowedDetents: [0.5, 1.0],
contentStyle: { backgroundColor: "transparent" },
}}
/>
- 在 iOS 26+ 上,使用
contentStyle: { backgroundColor: "transparent" }会使背景呈现液态玻璃效果。
常见的路由结构
一个包含标签页和每个标签页内堆栈的标准应用布局:
app/
_layout.tsx — <NativeTabs />
(index,search)/
_layout.tsx — <Stack />
index.tsx — 主列表
search.tsx — 搜索视图
// app/_layout.tsx
import { NativeTabs, Icon, Label } from "expo-router/unstable-native-tabs";
import { Theme } from "../components/theme";
export default function Layout() {
return (
<Theme>
<NativeTabs>
<NativeTabs.Trigger name="(index)">
<Icon sf="list.dash" />
<Label>项目</Label>
</NativeTabs.Trigger>
<NativeTabs.Trigger name="(search)" role="search" />
</NativeTabs>
</Theme>
);
}
创建一个共享组路由,以便两个标签页都能推送公共屏幕:
// app/(index,search)/_layout.tsx
import { Stack } from "expo-router/stack";
import { PlatformColor } from "react-native";
export default function Layout({ segment }) {
const screen = segment.match(/\((.*)\)/)?.[1]!;
const titles: Record<string, string> = { index: "项目", search: "搜索" };
return (
<Stack
screenOptions={{
headerTransparent: true,
headerShadowVisible: false,
headerLargeTitleShadowVisible: false,
headerLargeStyle: { backgroundColor: "transparent" },
headerTitleStyle: { color: PlatformColor("label") },
headerLargeTitle: true,
headerBlurEffect: "none",
headerBackButtonDisplayMode: "minimal",
}}
>
<Stack.Screen name={screen} options={{ title: titles[screen] }} />
<Stack.Screen name="i/[id]" options={{ headerLargeTitle: false }} />
</Stack>
);
}
📄 原始文档
完整文档(英文):
https://skills.sh/expo/skills/building-native-ui
💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。

评论(0)