🚀 快速安装

复制以下命令并运行,立即安装此 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:iosnpx expo run:android 之前:

  1. 从 Expo Go 开始:运行 npx expo start 并使用 Expo Go 扫描二维码
  2. 检查功能是否正常:在 Expo Go 中全面测试你的应用
  3. 仅在必要时创建自定义构建 – 请参阅下方说明

何时需要自定义构建

仅在以下情况需要使用 npx expo run:ios/androideas 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 原始英文文档,方便对照翻译。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。