关于 building-native-ui
building-native-ui 是面向 Expo 和 React Native 项目的界面构建指南,涵盖原生控件、样式、布局和交互。它提供参考资料与代码规范,并指导何时使用 Expo Go 或自定义构建。
主要功能
- 01原生控件与动画参考
- 02布局和响应式设计建议
- 03平台语义颜色用法
- 04样式与代码规范
- 05Expo Go 和自定义构建指引
适用场景
- 01为 Expo 路由构建界面
- 02实现原生控件与动画
- 03处理跨屏幕尺寸布局
- 04判断是否需要自定义原生构建
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ expo/ skills --skill building-native-ui安装后在 Claude Code 中输入/building-native-ui确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlreferences/animations.mdcontrols.mdgradients.mdicons.mdmedia.mdstorage.mdvisual-effects.mdwebgpu-three.md详细介绍
Expo UI 指南
对于路由、链接、堆栈、标签页、模态框、底部面板和标题栏,请使用 expo-router 技能。
参考资料
按需查阅以下资源:
references/
animations.md Reanimated: entering, exiting, layout, scroll-driven, gestures
controls.md Native iOS: Switch, Slider, SegmentedControl, DateTimePicker, Picker
gradients.md CSS gradients via experimental_backgroundImage (New Arch only)
icons.md SF Symbols via expo-image (sf: source), names, animations, weights
media.md Camera, audio, video, and file saving
storage.md SQLite, AsyncStorage, SecureStore
visual-effects.md Blur (expo-blur) and liquid glass (expo-glass-effect)
webgpu-three.md 3D graphics, games, GPU visualizations with WebGPU and Three.js
运行应用
重要:创建自定义构建前,务必先尝试使用 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实现的小组件、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 使用路径别名;重构时优先使用别名而非相对导入。
库的选择
- 不要使用 React Native 中已移除的模块,例如 Picker、WebView、SafeAreaView 或 AsyncStorage
- 不要使用旧版 expo-permissions
- 使用
expo-audio,而不是expo-av - 使用
expo-video,而不是expo-av - 对于 SF Symbols,使用
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实现液态玻璃背景 - 对于原生语义颜色,使用来自
expo-router的Color,而不是原始PlatformColor(类型安全,并会自动适配浅色/深色模式) - 在 SDK 56+ 中,不要直接从
@react-navigation/*导入——改用expo-router/react-navigation(涵盖@react-navigation/native、/core、/elements、/routers)
响应式布局
- 始终将根组件包裹在滚动视图中,以适应不同屏幕
- 使用
<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 - 向页面添加
ScrollView时,它几乎总应是路由组件内的第一个组件 - 对可能需要复制的数据文本,使用
<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,请使用内联样式
颜色
使用 expo-router 中的 Color API 获取原生语义颜色。它是 PlatformColor 的类型安全封装,通过 Color.ios.* 提供 iOS UIKit 颜色,通过 Color.android.material.*(静态)或 Color.android.dynamic.*(在 Android 12 及更高版本上适配用户壁纸)提供 Android Material 3 颜色。这些颜色会在设备上解析,并自动适配浅色/深色模式和辅助功能设置,因此无需再维护独立的浅色/深色十六进制颜色表或 colors.web.ts 文件。
Color 与平台相关,因此请将每个值包装在 Platform.select 中,并为 Web 提供默认十六进制颜色回退值。在 theme/colors.ts 中集中管理调色板,并在各处导入 colors:
// theme/colors.ts
import { Platform } from "react-native";
import { Color } from "expo-router";
export const colors = {
label: Platform.select({
ios: Color.ios.label,
android: Color.android.dynamic.onSurface,
default: "#000000",
})!,
secondaryLabel: Platform.select({
ios: Color.ios.secondaryLabel,
android: Color.android.dynamic.onSurfaceVariant,
default: "#3c3c43",
})!,
separator: Platform.select({
ios: Color.ios.separator,
android: Color.android.dynamic.outlineVariant,
default: "#c6c6c8",
})!,
systemBackground: Platform.select({
ios: Color.ios.systemBackground,
android: Color.android.dynamic.surface,
default: "#ffffff",
})!,
systemBlue: Platform.select({
ios: Color.ios.systemBlue,
android: Color.android.dynamic.primary,
default: "#007aff",
})!,
};
import { colors } from "@/theme/colors";
<View style={{ backgroundColor: colors.systemBackground }}>
<Text style={{ color: colors.label }}>Title</Text>
</View>;
- iOS 会在系统主题变化时自动重新解析这些颜色。在 Android 上,请在任何渲染这些颜色的组件中调用
useColorScheme(),这样主题切换时组件就会重新渲染(React Compiler 对组件进行记忆化时必须这样做)。 - 不要将
Color/PlatformColor值传入 Reanimated 样式中——请在其中使用静态颜色(参见references/animations.md)。 Platform.select({...})!返回string | OpaqueColorValue。大多数 React Native 样式属性都接受ColorValue(string | OpaqueColorValue),因此可以正常使用。但某些第三方属性只接受string(例如expo-image中的tintColor)。需要时请进行类型转换:colors.label as string。
文本样式
- 为每个显示重要数据或错误消息的
<Text/>元素添加selectable属性 - 计数器应使用
{ fontVariant: 'tabular-nums' }以保持对齐
阴影
使用 CSS boxShadow 样式属性。绝不要使用旧版 React Native 阴影或 elevation 样式。
<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />
支持 'inset' 阴影。