关于 building-ui
building-ui 是面向 Expo Router 与 React Native 界面开发的技能指南,涵盖路由、原生控件、样式和运行方式。它通过一组专题参考资料和明确的库选择与实现规范,支持构建符合 iOS 界面惯例的应用。
主要功能
- 01Expo Router 路由组织规范
- 02原生控件与导航建议
- 03动画、媒体及存储参考
- 04样式与响应式布局规则
- 05Expo Go 与自定义构建指导
适用场景
- 01规划 Expo 应用的路由结构
- 02为界面选择 Expo 与 React Native 库
- 03实现适配屏幕和安全区域的布局
- 04判断是否需要自定义原生构建
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ gigs-slc/ react-native-skills --skill building-ui安装后在 Claude Code 中输入/building-ui确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/animations.mdcontrols.mdform-sheet.mdgradients.mdicons.mdmedia.mdroute-structure.mdsearch.mdstorage.mdtabs.mdvisual-effects.mdwebgpu-three.md详细介绍
Expo UI 指南
参考资料
根据需要查阅以下资源:
- ./references/route-structure.md -- 路由文件约定、动态路由、查询参数、路由组和文件夹组织方式
- ./references/tabs.md -- 使用 NativeTabs 的原生标签栏、从 JS 标签栏迁移、iOS 26 功能
- ./references/icons.md -- 使用 expo-symbols 的 SF Symbols、常用图标名称、动画和字重
- ./references/controls.md -- 原生 iOS 控件:Switch、Slider、SegmentedControl、DateTimePicker、Picker
- ./references/visual-effects.md -- 使用 expo-blur 实现模糊效果,以及使用 expo-glass-effect 实现液态玻璃效果
- ./references/animations.md -- Reanimated 动画:进入、退出、布局、滚动驱动和手势
- ./references/search.md -- 搜索栏与标题栏的集成、useSearch 钩子和筛选模式
- ./references/gradients.md -- 使用 experimental_backgroundImage 实现 CSS 渐变(仅限新架构)
- ./references/media.md -- Expo Router 的媒体处理,包括相机、音频、视频和文件保存
- ./references/storage.md -- 数据存储模式,包括 SQLite、AsyncStorage 和 SecureStore
- ./references/webgpu-three.md -- 使用 WebGPU 和 Three.js 实现 3D 图形、游戏和 GPU 驱动的可视化效果
运行应用
重要:始终先尝试使用 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 targets(通过
@bacons/apple-targets实现的小组件、App Clip、扩展) - Expo Go 未包含的第三方原生模块
- 无法通过
app.json配置的自定义原生配置
Expo Go 支持的情况
Expo Go 开箱即支持大量功能:
- 所有
expo-*软件包(相机、定位、通知等) - Expo Router 导航
- 大多数 UI 库(reanimated、gesture handler 等)
- 推送通知、深层链接等
如果不确定,请先尝试 Expo Go。 创建自定义构建会增加复杂性、减慢迭代速度,并且需要配置 Xcode/Android Studio。
代码风格
- 注意检查未终止的字符串。确保转义嵌套的反引号;务必正确转义引号。
- 始终在文件顶部使用 import 语句。
- 文件名始终使用 kebab-case,例如
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 - 使用
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>,以更智能地调整安全区域内边距 - FlatList 和 SectionList 也应应用
contentInsetAdjustmentBehavior="automatic" - 使用 Flexbox,而不是 Dimensions API
- 测量屏幕尺寸时,始终优先使用
useWindowDimensions,而不是Dimensions.get()
行为
- 在 iOS 上有条件地使用 expo-haptics,打造更愉悦的体验
- 使用内置触觉反馈的视图,例如 React Native 中的
<Switch />和@react-native-community/datetimepicker - 当路由属于 Stack 时,其第一个子元素几乎总应为设置了
contentInsetAdjustmentBehavior="automatic"的 ScrollView - 优先在 Stack.Screen 选项中使用
headerSearchBarOptions添加搜索栏 - 对包含可复制数据的文本使用
<Text selectable />属性 - 考虑将较大的数字格式化为 1.4M 或 38k 等形式
- 除非在 webview 或 Expo DOM 组件中,否则绝不要使用 'img' 或 'div' 等内置元素
样式
遵循 Apple 人机界面指南。
通用样式规则
- 优先使用 Flex gap,而不是 margin 和 padding 样式
- 尽可能优先使用 padding,而不是 margin
- 始终考虑安全区域,可通过堆栈标题、标签页或 ScrollView/FlatList 的
contentInsetAdjustmentBehavior="automatic"实现 - 确保同时考虑顶部和底部安全区域内边距
- 除非复用样式能更快,否则使用内联样式,而不是 StyleSheet.create
- 为状态变化添加进入和退出动画
- 除非创建胶囊形状,否则圆角使用
{ borderCurve: 'continuous' } - 始终使用导航堆栈标题,而不是页面上的自定义文本元素
- 为 ScrollView 添加内边距时,使用
contentContainerStyle设置 padding 和 gap,而不是在 ScrollView 本身设置 padding(可减少裁切) - 不支持 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';
// Basic link
<Link href="/path" />
// Wrapping custom components
<Link href="/path" asChild>
<Pressable>...</Pressable>
</Link>
尽可能添加 <Link.Preview>,遵循 iOS 规范。多使用上下文菜单和预览来增强导航体验。
Stack
- 始终使用
_layout.tsx文件定义堆栈 - 使用 'expo-router/stack' 中的 Stack 创建原生导航堆栈
页面标题
在 Stack.Screen 选项中设置页面标题:
<Stack.Screen options={{ title: "Home" }} />
上下文菜单
为 Link 组件添加长按上下文菜单:
import { Link } from "expo-router";
<Link href="/settings" asChild>
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Menu>
<Link.MenuAction
title="Share"
icon="square.and.arrow.up"
onPress={handleSharePress}
/>
<Link.MenuAction
title="Block"
icon="nosign"
destructive
onPress={handleBlockPress}
/>
<Link.Menu title="More" icon="ellipsis">
<Link.MenuAction title="Copy" icon="doc.on.doc" onPress={() => {}} />
<Link.MenuAction
title="Delete"
icon="trash"
destructive
onPress={() => {}}
/>
</Link.Menu>
</Link.Menu>
</Link>;
链接预览
多使用链接预览来增强导航体验:
<Link href="/settings">
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Preview />
</Link>
链接预览可与上下文菜单搭配使用。
模态框
将屏幕作为模态框呈现:
<Stack.Screen name="modal" options={{ presentation: "modal" }} />
相比构建自定义模态框组件,优先使用此方式。
Sheet
将屏幕作为动态表单 Sheet 呈现:
<Stack.Screen
name="sheet"
options={{
presentation: "formSheet",
sheetGrabberVisible: true,
sheetAllowedDetents: [0.5, 1.0],
contentStyle: { backgroundColor: "transparent" },
}}
/>
- 使用
contentStyle: { backgroundColor: "transparent" }可在 iOS 26+ 上让背景呈现 Liquid Glass 效果。
常见路由结构
在每个标签页中包含堆栈的标准应用布局:
app/
_layout.tsx — <NativeTabs />
(index,search)/
_layout.tsx — <Stack />
index.tsx — Main list
search.tsx — Search view
// 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>Items</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: "Items", search: "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>
);
}