Chapter 02

create-expo-app:5 分钟跑起来

Node 20+、iOS 模拟器或真机 Expo Go app(iOS/Android 都有),一条命令生成项目,就能看到红色感叹号变绿色对勾。

脚手架

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 遇到同级目录下的 _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

开发工具

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。

本章小结