expo-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWhen to use
适用场景
Use this skill when working with Expo or React Native code. It teaches current best practices and
prevents common mistakes that AI agents make with outdated patterns, manual native setup, and
deprecated APIs.
当你处理Expo或React Native代码时可以使用这份指南,它讲解了当前的最佳实践,避免AI Agent使用过时模式、手动原生配置以及废弃API所导致的常见错误。
Critical Rules
核心规则
1. Use Expo Router for navigation
1. 使用Expo Router实现导航
Wrong (agents do this):
tsx
import { createStackNavigator } from "@react-navigation/stack";
const Stack = createStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}Correct:
app/
_layout.tsx
index.tsx
settings.tsxUse the directory convention. Files become routes automatically. New Expo projects include
Expo Router by default.
app/Why: Expo Router is built on React Navigation, provides file-based routing, typed routes, deep
linking, and integrates with Expo CLI. Manual React Navigation setup is redundant and misses Expo
Router features.
错误写法(Agent常犯的问题):
tsx
import { createStackNavigator } from "@react-navigation/stack";
const Stack = createStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}正确写法:
app/
_layout.tsx
index.tsx
settings.tsx遵循目录约定,文件会自动成为路由。新的Expo项目默认已经集成了Expo Router。
app/原因: Expo Router基于React Navigation构建,提供了文件路由、类型化路由、深度链接能力,并且与Expo CLI深度集成。手动配置React Navigation是冗余操作,还会错失Expo Router的各项特性。
2. Use expo-router layouts for navigation structure
2. 使用expo-router布局文件定义导航结构
Wrong:
tsx
// Each screen manually defines its own layoutCorrect:
tsx
// app/_layout.tsx
import { Stack } from "expo-router";
export default function RootLayout() {
return <Stack />;
}Why: defines shared navigation structure, tabs, stacks, and nested layouts. It
centralizes navigation configuration.
_layout.tsx错误写法:
tsx
// Each screen manually defines its own layout正确写法:
tsx
// app/_layout.tsx
import { Stack } from "expo-router";
export default function RootLayout() {
return <Stack />;
}原因: 用于定义共享的导航结构、标签栏、堆栈以及嵌套布局,可以集中管理导航配置。
_layout.tsx3. Use EAS Build for native builds
3. 使用EAS Build做原生构建
Wrong:
bash
expo build:ios
expo build:androidCorrect:
bash
eas build --platform ios
eas build --platform androidWhy: is deprecated. EAS Build is the current cloud build service with better
caching, credentials management, and native module support.
expo build错误写法:
bash
expo build:ios
expo build:android正确写法:
bash
eas build --platform ios
eas build --platform android原因: 已经废弃,EAS Build是当前官方的云构建服务,拥有更好的缓存、凭证管理以及原生模块支持能力。
expo build4. Use Expo config plugins for native configuration
4. 使用Expo配置插件处理原生配置
Wrong:
bash
undefined错误写法:
bash
undefinedManually editing
Manually editing
ios/MyApp/Info.plist
android/app/src/main/AndroidManifest.xml
**Correct:**
```ts
// app.config.ts
import { ConfigPlugin } from "expo/config-plugins";
const withMyApiKey: ConfigPlugin<{ apiKey: string }> = (config, { apiKey }) => {
config = withInfoPlist(config, (c) => {
c.modResults["MY_API_KEY"] = apiKey;
return c;
});
return config;
};
export default {
plugins: [["withMyApiKey", { apiKey: process.env.API_KEY }]],
};Why: Direct edits to native files are overwritten on prebuild. Config plugins modify native
files at build time and survive prebuild.
ios/MyApp/Info.plist
android/app/src/main/AndroidManifest.xml
**正确写法:**
```ts
// app.config.ts
import { ConfigPlugin } from "expo/config-plugins";
const withMyApiKey: ConfigPlugin<{ apiKey: string }> = (config, { apiKey }) => {
config = withInfoPlist(config, (c) => {
c.modResults["MY_API_KEY"] = apiKey;
return c;
});
return config;
};
export default {
plugins: [["withMyApiKey", { apiKey: process.env.API_KEY }]],
};原因: 直接修改原生文件会在预构建时被覆盖,配置插件会在构建时修改原生文件,在预构建后仍然生效。
5. Platform-specific code: use platform extensions or Platform.select
5. 平台特定代码:使用平台扩展名或Platform.select
Wrong:
tsx
if (Platform.OS === "ios") {
return <IOSComponent />;
}
return <AndroidComponent />;Correct:
MyComponent.ios.tsx
MyComponent.android.tsxOr:
tsx
import { Platform } from "react-native";
const styles = StyleSheet.create({
container: Platform.select({
ios: { paddingTop: 20 },
android: { paddingTop: 0 },
}),
});Why: Platform file extensions let the bundler include only the correct code per platform.
Platform.select keeps conditional logic in one file when appropriate.
错误写法:
tsx
if (Platform.OS === "ios") {
return <IOSComponent />;
}
return <AndroidComponent />;正确写法:
MyComponent.ios.tsx
MyComponent.android.tsx或者:
tsx
import { Platform } from "react-native";
const styles = StyleSheet.create({
container: Platform.select({
ios: { paddingTop: 20 },
android: { paddingTop: 0 },
}),
});原因: 平台文件扩展名可以让打包工具只引入对应平台的正确代码,适合的场景下使用Platform.select可以把条件逻辑统一放在单个文件中。
6. Use expo-image instead of React Native Image
6. 使用expo-image替代React Native原生Image
Wrong:
tsx
import { Image } from "react-native";
<Image source={{ uri: url }} style={styles.image} />;Correct:
tsx
import { Image } from "expo-image";
<Image source={url} contentFit="cover" style={styles.image} />;Why: expo-image adds caching, placeholders, blurhash, and better performance. React Native's
Image lacks these features.
错误写法:
tsx
import { Image } from "react-native";
<Image source={{ uri: url }} style={styles.image} />;正确写法:
tsx
import { Image } from "expo-image";
<Image source={url} contentFit="cover" style={styles.image} />;原因: expo-image额外提供了缓存、占位图、blurhash以及更好的性能表现,React Native原生Image不具备这些特性。
7. Use expo-font for custom fonts
7. 使用expo-font加载自定义字体
Wrong:
bash
undefined错误写法:
bash
undefinedManual font linking via native projects
Manual font linking via native projects
**Correct:**
```tsx
import * as Font from "expo-font";
await Font.loadAsync({
CustomFont: require("./assets/fonts/CustomFont.ttf"),
});Why: expo-font handles loading and avoids manual native configuration.
**正确写法:**
```tsx
import * as Font from "expo-font";
await Font.loadAsync({
CustomFont: require("./assets/fonts/CustomFont.ttf"),
});原因: expo-font会自动处理字体加载,无需手动修改原生配置。
8. Use expo-secure-store for sensitive data
8. 使用expo-secure-store存储敏感数据
Wrong:
tsx
import AsyncStorage from "@react-native-async-storage/async-storage";
await AsyncStorage.setItem("authToken", token);Correct:
tsx
import * as SecureStore from "expo-secure-store";
await SecureStore.setItemAsync("authToken", token);Why: AsyncStorage is not encrypted. expo-secure-store uses the platform keychain/Keystore for
tokens and secrets.
错误写法:
tsx
import AsyncStorage from "@react-native-async-storage/async-storage";
await AsyncStorage.setItem("authToken", token);正确写法:
tsx
import * as SecureStore from "expo-secure-store";
await SecureStore.setItemAsync("authToken", token);原因: AsyncStorage没有加密,expo-secure-store使用平台的keychain/Keystore来存储token和密钥。
9. Use expo-notifications for push notifications
9. 使用expo-notifications实现推送通知
Wrong:
tsx
// Manual Firebase/APNs setup, native configurationCorrect:
tsx
import * as Notifications from "expo-notifications";
const token = await Notifications.getExpoPushTokenAsync();Why: expo-notifications abstracts push setup across iOS and Android. Manual Firebase/APNs
configuration is error-prone and platform-specific.
错误写法:
tsx
// Manual Firebase/APNs setup, native configuration正确写法:
tsx
import * as Notifications from "expo-notifications";
const token = await Notifications.getExpoPushTokenAsync();原因: expo-notifications封装了iOS和Android两端的推送配置逻辑,手动配置Firebase/APNs容易出错,且强依赖平台特性。
10. Use app.json or app.config.ts for configuration
10. 使用app.json或app.config.ts做配置管理
Wrong:
bash
undefined错误写法:
bash
undefinedEditing Info.plist or AndroidManifest.xml directly
Editing Info.plist or AndroidManifest.xml directly
**Correct:**
```json
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"plugins": ["expo-router"]
}
}Why: Expo manages native config from app.json/app.config.ts. Direct edits are overwritten on
prebuild.
**正确写法:**
```json
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"plugins": ["expo-router"]
}
}原因: Expo会从app.json/app.config.ts中读取配置来管理原生配置,直接修改原生配置文件会在预构建时被覆盖。
11. Use expo-updates for OTA updates
11. 使用expo-updates实现OTA更新
Wrong:
tsx
import codePush from "react-native-code-push";
export default codePush(MyApp);Correct:
tsx
import * as Updates from "expo-updates";
await Updates.checkForUpdateAsync();Why: expo-updates integrates with EAS and Expo's update service. CodePush requires separate
setup and is redundant in Expo projects.
错误写法:
tsx
import codePush from "react-native-code-push";
export default codePush(MyApp);正确写法:
tsx
import * as Updates from "expo-updates";
await Updates.checkForUpdateAsync();原因: expo-updates与EAS和Expo的更新服务深度集成,CodePush需要单独配置,在Expo项目中属于冗余方案。
12. Use TypeScript with typed navigation params
12. 结合TypeScript使用类型化导航参数
Wrong:
tsx
router.push("/profile");Correct:
tsx
import { router } from "expo-router";
router.push({ pathname: "/profile/[id]", params: { id: userId } });Define route params in your route files. Expo Router provides typed route helpers when used with
TypeScript.
Why: Typed params prevent runtime errors and improve editor support.
错误写法:
tsx
router.push("/profile");正确写法:
tsx
import { router } from "expo-router";
router.push({ pathname: "/profile/[id]", params: { id: userId } });在路由文件中定义路由参数,Expo Router配合TypeScript使用时会提供类型化路由辅助方法。
原因: 类型化参数可以避免运行时错误,提升编辑器支持体验。
Patterns
推荐模式
- Create routes under with file names that map to URLs
app/ - Use for shared layout, tabs, and stacks
_layout.tsx - Put platform-specific components in and
Component.ios.tsxComponent.android.tsx - Use in
expo config plugins/app.jsonfor any native config changeapp.config.ts - Use for EAS Build profiles
eas.json
- 在目录下创建与URL映射的文件名来定义路由
app/ - 使用定义共享布局、标签栏和堆栈导航
_layout.tsx - 平台特定的组件放在和
Component.ios.tsx文件中Component.android.tsx - 任何原生配置修改都通过/
app.json中的app.config.ts实现expo config plugins - 使用配置EAS Build构建配置
eas.json
Anti-Patterns
反模式
- Do not set up React Navigation manually in Expo projects
- Do not use (use EAS Build)
expo build - Do not edit or
ios/directly in managed workflowandroid/ - Do not use AsyncStorage for auth tokens or secrets
- Do not use React Native's Image when expo-image is available
- Do not use CodePush in Expo projects (use expo-updates)
- 不要在Expo项目中手动配置React Navigation
- 不要使用(请使用EAS Build)
expo build - 不要在托管工作流中直接修改或
ios/目录下的文件android/ - 不要使用AsyncStorage存储鉴权token或密钥
- 当可以使用expo-image时不要使用React Native原生Image
- 不要在Expo项目中使用CodePush(请使用expo-updates)