关于 expo-best-practices
供处理 Expo 或 React Native 代码的 AI Agent 使用,归纳导航、原生配置、平台专属代码、存储和更新等开发任务的当前实践。
主要功能
- 01使用 Expo Router 实现文件路由
- 02通过配置插件设置原生项目
- 03使用 EAS Build 构建原生应用
- 04采用平台专属代码模式
- 05使用 Expo 包处理图片、字体和机密数据
- 06使用带类型的导航参数
适用场景
- 01组织路由和共享布局
- 02避免直接编辑文件来配置原生设置
- 03处理敏感数据与推送通知
- 04使用当前 API 维护 Expo 应用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ofershap/ expo-best-practices --skill expo-best-practices安装后在 Claude Code 中输入/expo-best-practices确认已加载。
详细介绍
适用场景
在处理 Expo 或 React Native 代码时使用此技能。它介绍当前的最佳实践,并避免 AI Agent 因使用过时模式、手动进行原生配置和调用已弃用 API 而犯下的常见错误。
关键规则
1. 使用 Expo Router 进行导航
错误(Agent 常见做法):
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
使用 app/ 目录约定。文件会自动成为路由。新的 Expo 项目默认包含 Expo Router。
原因: Expo Router 基于 React Navigation 构建,提供基于文件的路由、类型化路由、深度链接,并与 Expo CLI 集成。手动设置 React Navigation 不仅多余,还会错过 Expo Router 的功能。
2. 使用 expo-router 布局定义导航结构
错误:
// Each screen manually defines its own layout
正确:
// app/_layout.tsx
import { Stack } from "expo-router";
export default function RootLayout() {
return <Stack />;
}
原因: _layout.tsx 定义共享导航结构、标签页、堆栈和嵌套布局。它能集中管理导航配置。
3. 使用 EAS Build 构建原生应用
错误:
expo build:ios
expo build:android
正确:
eas build --platform ios
eas build --platform android
原因: expo build 已弃用。EAS Build 是当前的云构建服务,提供更好的缓存、凭据管理和原生模块支持。
4. 使用 Expo 配置插件进行原生配置
错误:
# Manually editing
ios/MyApp/Info.plist
android/app/src/main/AndroidManifest.xml
正确:
// 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.select
错误:
if (Platform.OS === "ios") {
return <IOSComponent />;
}
return <AndroidComponent />;
正确:
MyComponent.ios.tsx
MyComponent.android.tsx
或者:
import { Platform } from "react-native";
const styles = StyleSheet.create({
container: Platform.select({
ios: { paddingTop: 20 },
android: { paddingTop: 0 },
}),
});
原因: 平台文件扩展名让打包器只为相应平台包含正确的代码。在适当的情况下,Platform.select 可将条件逻辑保留在同一文件中。
6. 使用 expo-image 替代 React Native Image
错误:
import { Image } from "react-native";
<Image source={{ uri: url }} style={styles.image} />;
正确:
import { Image } from "expo-image";
<Image source={url} contentFit="cover" style={styles.image} />;
原因: expo-image 提供缓存、占位图、blurhash 和更好的性能。React Native 的 Image 不具备这些功能。
7. 使用 expo-font 加载自定义字体
错误:
# Manual font linking via native projects
正确:
import * as Font from "expo-font";
await Font.loadAsync({
CustomFont: require("./assets/fonts/CustomFont.ttf"),
});
原因: expo-font 负责处理字体加载,避免手动配置原生端。
8. 使用 expo-secure-store 存储敏感数据
错误:
import AsyncStorage from "@react-native-async-storage/async-storage";
await AsyncStorage.setItem("authToken", token);
正确:
import * as SecureStore from "expo-secure-store";
await SecureStore.setItemAsync("authToken", token);
原因: AsyncStorage 不会加密。expo-secure-store 使用平台的 keychain/Keystore 存储令牌和机密信息。
9. 使用 expo-notifications 发送推送通知
错误:
// Manual Firebase/APNs setup, native configuration
正确:
import * as Notifications from "expo-notifications";
const token = await Notifications.getExpoPushTokenAsync();
原因: expo-notifications 抽象了 iOS 和 Android 上的推送设置流程。手动配置 Firebase/APNs 容易出错,而且因平台而异。
10. 使用 app.json 或 app.config.ts 进行配置
错误:
# Editing Info.plist or AndroidManifest.xml directly
正确:
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"plugins": ["expo-router"]
}
}
原因: Expo 根据 app.json/app.config.ts 管理原生配置。直接编辑原生文件会在预构建时被覆盖。
11. 使用 expo-updates 进行 OTA 更新
错误:
import codePush from "react-native-code-push";
export default codePush(MyApp);
正确:
import * as Updates from "expo-updates";
await Updates.checkForUpdateAsync();
原因: expo-updates 与 EAS 和 Expo 的更新服务集成。CodePush 需要单独设置,在 Expo 项目中属于多余方案。
12. 使用 TypeScript 和类型化导航参数
错误:
router.push("/profile");
正确:
import { router } from "expo-router";
router.push({ pathname: "/profile/[id]", params: { id: userId } });
在路由文件中定义路由参数。与 TypeScript 配合使用时,Expo Router 会提供类型化路由辅助工具。
原因: 类型化参数可避免运行时错误,并改善编辑器支持。
模式
- 在
app/下创建路由,使用映射到 URL 的文件名 - 使用
_layout.tsx定义共享布局、标签页和堆栈 - 将平台专属组件放在
Component.ios.tsx和Component.android.tsx中 - 任何原生配置变更都使用
app.json/app.config.ts中的expo config plugins - 使用
eas.json配置 EAS Build 配置文件
反模式
- 不要在 Expo 项目中手动设置 React Navigation
- 不要使用
expo build(请使用 EAS Build) - 在托管工作流中,不要直接编辑
ios/或android/ - 不要使用 AsyncStorage 存储身份验证令牌或机密信息
- expo-image 可用时,不要使用 React Native 的 Image
- 不要在 Expo 项目中使用 CodePush(请使用 expo-updates)