脚手架
npx create-expo-app@latest my-app # 选模板: Default(带 Expo Router + TypeScript) cd my-app npx expo start # 按 i 开 iOS 模拟器,按 a 开 Android,按 w 开 Web
首次启动会装 Metro bundler,下载 JS bundle。iOS 默认开 Xcode 模拟器,Android 开 Android Studio emulator 或 USB 真机。Web 开 http://localhost:8081。
目录结构
my-app/ ├── app/ ← 路由目录(核心) │ ├── _layout.tsx ← Root Layout(相当于 App.tsx) │ ├── index.tsx ← / │ ├── +not-found.tsx ← 404 │ └── (tabs)/ ← 分组路由 │ ├── _layout.tsx ← Tabs 布局 │ ├── index.tsx ← /(tabs) │ └── explore.tsx ← /explore ├── assets/ ← 图片、字体、音频 │ ├── fonts/ │ └── images/ ├── components/ ← 可复用组件(约定,可自定义) ├── constants/ ← 颜色、配置 ├── hooks/ ← 自定义 hooks ├── app.json ← Expo 配置(bundleId、icon、permission) ├── package.json └── tsconfig.json
app.json:单一配置源
{
"expo": {
"name": "my-app",
"slug": "my-app",
"scheme": "myapp",
"version": "1.0.0",
"ios": { "bundleIdentifier": "com.gufa.myapp" },
"android": { "package": "com.gufa.myapp" },
"plugins": ["expo-router"],
"experiments": { "typedRoutes": true }
}
}
bundleId / package / scheme(Deep Link 协议)都写在这里。改完跑 npx expo prebuild 重新生成 ios/android 目录(EAS Build 会自动做)。
app/_layout.tsx 根布局
import { Stack } from 'expo-router'; export default function RootLayout() { return ( <Stack screenOptions={{ headerShown: true }}> <Stack.Screen name="index" options={{ title: '首页' }} /> <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> </Stack> ); }
_layout.tsx 就是布局组件
Expo Router 遇到同级目录下的
Expo Router 遇到同级目录下的
_layout.tsx 就把它作为这一层的父组件包裹所有子路由。不写 _layout 的目录,子路由直接嵌入上层布局。
app/index.tsx:首屏
import { View, Text, StyleSheet } from 'react-native'; import { Link } from 'expo-router'; export default function Home() { return ( <View style={styles.container}> <Text style={styles.title}>Hello Expo Router!</Text> <Link href="/settings">去设置</Link> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center' }, title: { fontSize: 24, fontWeight: '600' }, });
添加一个页面
# 新建 app/settings.tsx,保存,热重载会立刻出现 /settings 路由
// app/settings.tsx import { View, Text, Switch } from 'react-native'; import { useState } from 'react'; export default function Settings() { const [on, setOn] = useState(true); return ( <View style={{ padding: 16 }}> <Text>深色模式</Text> <Switch value={on} onValueChange={setOn} /> </View> ); }
导航 API 三兄弟
<Link href="...">
声明式跳转,像 Web 的
<a>。支持 asChild 把 Pressable 包装成可点链接。router.push / replace / back
命令式跳转,适合事件处理里。
import { router } from 'expo-router'。useRouter()
Hook 版,组件内用。返回同样的 router 对象,支持 SSR/热重载。
热重载与 Fast Refresh
- 保存文件 → Metro 检测 → 秒级热替换,保持组件状态
- 改 _layout 或路由结构,需要完整重载一次
- 改 app.json 要重新
npx expo start -c清缓存 - Native 代码(config plugin / 新增包)要
npx expo prebuild --clean重建
开发工具
Expo Go App
手机装一个 Expo Go,扫二维码直接跑(无需 Xcode)。适合 demo,但自定义 native 模块不支持。
Development Build
更专业的方案,
eas build --profile development 做一个包含 dev-client 的自定义包,支持所有原生。生产团队必备。React DevTools / Flipper 替代
按 j 打开 debugger,新版用 Chrome DevTools 直接调试 React / Network。
常见坑
Metro 缓存
改路由结构不生效?
npx expo start -c 清缓存 + 重启。Node 版本
Node 20 LTS 最稳。18 以下 SDK 52 可能起不来,22 可以但 native 模块偶有问题。
Monorepo 兼容
yarn/pnpm workspace 需要
expo-modules-core 提升,metro.config.js 加 watchFolders。本章小结
npx create-expo-app一条命令出项目,默认带 Router + TSapp/目录是路由核心,_layout.tsx是这层的父布局app.json是单一配置源:bundleId、scheme、permissions、插件- 三种导航 API:
<Link>、router、useRouter() - 开发优先 Expo Go,生产用 EAS Development Build