expo-framework-rule
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseExpo Framework Rule Skill
Expo Framework 编码规范指南
<identity>
You are a coding standards expert specializing in expo framework rule.
You help developers write better code by applying established guidelines and best practices.
</identity>
<capabilities>
- Review code for guideline compliance
- Suggest improvements based on best practices
- Explain why certain patterns are preferred
- Help refactor code to meet standards
</capabilities>
<instructions>
When reviewing or writing code, apply these comprehensive Expo framework guidelines.
<identity>
你是一名专注于Expo Framework规范的编码标准专家。
你通过应用既定的指南和最佳实践,帮助开发者编写更优质的代码。
</identity>
<capabilities>
- 检查代码是否符合指南要求
- 基于最佳实践提出改进建议
- 解释为何优先选择某些模式
- 协助重构代码以符合标准
</capabilities>
<instructions>
在审查或编写代码时,请遵循这些全面的Expo Framework指南。
Expo SDK Features and APIs
Expo SDK 功能与API
Core Expo Modules
核心Expo模块
FileSystem:
typescript
import * as FileSystem from 'expo-file-system';
// Read file
const content = await FileSystem.readAsStringAsync(FileSystem.documentDirectory + 'file.txt');
// Download file
const download = await FileSystem.downloadAsync(
'https://example.com/file.pdf',
FileSystem.documentDirectory + 'file.pdf'
);Camera:
typescript
import { CameraView, useCameraPermissions } from 'expo-camera';
function CameraScreen() {
const [permission, requestPermission] = useCameraPermissions();
if (!permission?.granted) {
return <Button onPress={requestPermission} title="Grant Permission" />;
}
return (
<CameraView
style={{ flex: 1 }}
onBarcodeScanned={({ data }) => console.log(data)}
/>
);
}Location:
typescript
import * as Location from 'expo-location';
const getLocation = async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
return;
}
const location = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.High,
});
return location.coords;
};Notifications:
typescript
import * as Notifications from 'expo-notifications';
// Configure notification handler
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
// Schedule notification
await Notifications.scheduleNotificationAsync({
content: {
title: 'Reminder',
body: 'Time to check your app!',
},
trigger: { seconds: 60 },
});FileSystem:
typescript
import * as FileSystem from 'expo-file-system';
// Read file
const content = await FileSystem.readAsStringAsync(FileSystem.documentDirectory + 'file.txt');
// Download file
const download = await FileSystem.downloadAsync(
'https://example.com/file.pdf',
FileSystem.documentDirectory + 'file.pdf'
);Camera:
typescript
import { CameraView, useCameraPermissions } from 'expo-camera';
function CameraScreen() {
const [permission, requestPermission] = useCameraPermissions();
if (!permission?.granted) {
return <Button onPress={requestPermission} title="Grant Permission" />;
}
return (
<CameraView
style={{ flex: 1 }}
onBarcodeScanned={({ data }) => console.log(data)}
/>
);
}Location:
typescript
import * as Location from 'expo-location';
const getLocation = async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
return;
}
const location = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.High,
});
return location.coords;
};Notifications:
typescript
import * as Notifications from 'expo-notifications';
// Configure notification handler
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
// Schedule notification
await Notifications.scheduleNotificationAsync({
content: {
title: 'Reminder',
body: 'Time to check your app!',
},
trigger: { seconds: 60 },
});Asset Management
资源管理
typescript
import { Image } from 'expo-image';
import { Asset } from 'expo-asset';
// Preload assets
await Asset.loadAsync([
require('./assets/logo.png'),
require('./assets/background.jpg'),
]);
// Optimized image component
<Image
source={require('./assets/photo.jpg')}
contentFit="cover"
transition={200}
style={{ width: 200, height: 200 }}
/>typescript
import { Image } from 'expo-image';
import { Asset } from 'expo-asset';
// Preload assets
await Asset.loadAsync([
require('./assets/logo.png'),
require('./assets/background.jpg'),
]);
// Optimized image component
<Image
source={require('./assets/photo.jpg')}
contentFit="cover"
transition={200}
style={{ width: 200, height: 200 }}
/>SQLite Database
SQLite数据库
typescript
import * as SQLite from 'expo-sqlite';
const db = await SQLite.openDatabaseAsync('mydb.db');
// Create table
await db.execAsync(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE
);
`);
// Insert data
await db.runAsync('INSERT INTO users (name, email) VALUES (?, ?)', 'John', 'john@example.com');
// Query data
const users = await db.getAllAsync('SELECT * FROM users');typescript
import * as SQLite from 'expo-sqlite';
const db = await SQLite.openDatabaseAsync('mydb.db');
// Create table
await db.execAsync(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE
);
`);
// Insert data
await db.runAsync('INSERT INTO users (name, email) VALUES (?, ?)', 'John', 'john@example.com');
// Query data
const users = await db.getAllAsync('SELECT * FROM users');EAS Build and Submit
EAS构建与提交
eas.json Configuration
eas.json配置
json
{
"cli": {
"version": ">= 5.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"channel": "development",
"ios": {
"simulator": true
}
},
"preview": {
"distribution": "internal",
"channel": "preview",
"android": {
"buildType": "apk"
}
},
"production": {
"channel": "production",
"autoIncrement": true,
"env": {
"API_URL": "https://api.production.com"
}
}
},
"submit": {
"production": {
"ios": {
"ascAppId": "1234567890",
"appleId": "user@example.com"
},
"android": {
"serviceAccountKeyPath": "./google-service-account.json",
"track": "production"
}
}
}
}json
{
"cli": {
"version": ">= 5.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"channel": "development",
"ios": {
"simulator": true
}
},
"preview": {
"distribution": "internal",
"channel": "preview",
"android": {
"buildType": "apk"
}
},
"production": {
"channel": "production",
"autoIncrement": true,
"env": {
"API_URL": "https://api.production.com"
}
}
},
"submit": {
"production": {
"ios": {
"ascAppId": "1234567890",
"appleId": "user@example.com"
},
"android": {
"serviceAccountKeyPath": "./google-service-account.json",
"track": "production"
}
}
}
}Build Commands
构建命令
bash
undefinedbash
undefinedDevelopment build
Development build
eas build --profile development --platform ios
eas build --profile development --platform ios
Preview build (internal testing)
Preview build (internal testing)
eas build --profile preview --platform android
eas build --profile preview --platform android
Production build
Production build
eas build --profile production --platform all
eas build --profile production --platform all
Build and auto-submit
Build and auto-submit
eas build --profile production --auto-submit
undefinedeas build --profile production --auto-submit
undefinedBuild Environment Variables
构建环境变量
json
{
"build": {
"production": {
"env": {
"API_URL": "https://api.prod.com",
"SENTRY_DSN": "https://..."
}
}
}
}Access in app:
typescript
const apiUrl = process.env.EXPO_PUBLIC_API_URL;json
{
"build": {
"production": {
"env": {
"API_URL": "https://api.prod.com",
"SENTRY_DSN": "https://..."
}
}
}
}在应用中访问:
typescript
const apiUrl = process.env.EXPO_PUBLIC_API_URL;Over-the-Air (OTA) Updates
空中(OTA)更新
EAS Update Configuration
EAS更新配置
json
{
"expo": {
"runtimeVersion": {
"policy": "appVersion"
},
"updates": {
"url": "https://u.expo.dev/[project-id]"
}
}
}json
{
"expo": {
"runtimeVersion": {
"policy": "appVersion"
},
"updates": {
"url": "https://u.expo.dev/[project-id]"
}
}
}Publishing Updates
发布更新
bash
undefinedbash
undefinedPublish to production channel
Publish to production channel
eas update --channel production --message "Fix login bug"
eas update --channel production --message "Fix login bug"
Publish to preview
Publish to preview
eas update --channel preview --message "Test new feature"
eas update --channel preview --message "Test new feature"
View update history
View update history
eas update:list --channel production
undefinedeas update:list --channel production
undefinedUpdate Channels Strategy
更新渠道策略
javascript
// Different channels for different environments
production -> main branch
staging -> develop branch
preview -> feature branchesjavascript
// Different channels for different environments
production -> main branch
staging -> develop branch
preview -> feature branchesChecking for Updates in App
在应用中检查更新
typescript
import * as Updates from 'expo-updates';
async function checkForUpdates() {
if (!__DEV__) {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}
}
}
// Check on app focus
useEffect(() => {
const subscription = AppState.addEventListener('change', state => {
if (state === 'active') {
checkForUpdates();
}
});
return () => subscription.remove();
}, []);typescript
import * as Updates from 'expo-updates';
async function checkForUpdates() {
if (!__DEV__) {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}
}
}
// Check on app focus
useEffect(() => {
const subscription = AppState.addEventListener('change', state => {
if (state === 'active') {
checkForUpdates();
}
});
return () => subscription.remove();
}, []);Runtime Version Management
运行时版本管理
json
{
"expo": {
"runtimeVersion": "1.0.0"
}
}Only compatible OTA updates will be delivered to builds with matching runtime versions.
json
{
"expo": {
"runtimeVersion": "1.0.0"
}
}只有与运行时版本匹配的OTA更新才会推送给对应的应用构建版本。
Native Module Integration
原生模块集成
Custom Native Modules with Expo Modules API
使用Expo Modules API开发自定义原生模块
typescript
// ios/MyModule.swift
import ExpoModulesCore
public class MyModule: Module {
public func definition() -> ModuleDefinition {
Name("MyModule")
Function("hello") { (name: String) -> String in
return "Hello \(name)!"
}
AsyncFunction("fetchData") { (url: String, promise: Promise) in
// Async operation
promise.resolve(["data": "value"])
}
}
}typescript
// Usage in JavaScript
import { NativeModules } from 'react-native';
const { MyModule } = NativeModules;
const greeting = MyModule.hello('World');typescript
// ios/MyModule.swift
import ExpoModulesCore
public class MyModule: Module {
public func definition() -> ModuleDefinition {
Name("MyModule")
Function("hello") { (name: String) -> String in
return "Hello \(name)!"
}
AsyncFunction("fetchData") { (url: String, promise: Promise) in
// Async operation
promise.resolve(["data": "value"])
}
}
}typescript
// Usage in JavaScript
import { NativeModules } from 'react-native';
const { MyModule } = NativeModules;
const greeting = MyModule.hello('World');Config Plugins
配置插件
Create custom config plugin for native configuration:
javascript
// app-plugin.js
const { withAndroidManifest } = require('@expo/config-plugins');
const withCustomManifest = config => {
return withAndroidManifest(config, async config => {
const androidManifest = config.modResults;
// Modify manifest
androidManifest.manifest.application[0].$['android:usesCleartextTraffic'] = 'true';
return config;
});
};
module.exports = withCustomManifest;Apply in app.json:
json
{
"expo": {
"plugins": ["./app-plugin.js"]
}
}创建用于原生配置的自定义插件:
javascript
// app-plugin.js
const { withAndroidManifest } = require('@expo/config-plugins');
const withCustomManifest = config => {
return withAndroidManifest(config, async config => {
const androidManifest = config.modResults;
// Modify manifest
androidManifest.manifest.application[0].$['android:usesCleartextTraffic'] = 'true';
return config;
});
};
module.exports = withCustomManifest;在app.json中应用:
json
{
"expo": {
"plugins": ["./app-plugin.js"]
}
}Using Third-Party Native Libraries
使用第三方原生库
Without Custom Native Code (Recommended):
bash
npx expo install react-native-reanimatedWith Custom Native Code:
bash
npx expo install react-native-camera
npx expo prebuild无自定义原生代码(推荐):
bash
npx expo install react-native-reanimated含自定义原生代码:
bash
npx expo install react-native-camera
npx expo prebuildApp Configuration (app.json / app.config.js)
应用配置(app.json / app.config.js)
Static Configuration (app.json)
静态配置(app.json)
json
{
"expo": {
"name": "My App",
"slug": "my-app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.company.myapp",
"buildNumber": "1.0.0",
"infoPlist": {
"NSCameraUsageDescription": "We need camera access for photos",
"NSLocationWhenInUseUsageDescription": "Location for nearby features"
}
},
"android": {
"package": "com.company.myapp",
"versionCode": 1,
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"permissions": ["CAMERA", "ACCESS_FINE_LOCATION"]
},
"web": {
"favicon": "./assets/favicon.png",
"bundler": "metro"
},
"plugins": [
"expo-router",
[
"expo-camera",
{
"cameraPermission": "Allow $(PRODUCT_NAME) to access camera"
}
]
],
"extra": {
"apiUrl": "https://api.example.com"
}
}
}json
{
"expo": {
"name": "My App",
"slug": "my-app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.company.myapp",
"buildNumber": "1.0.0",
"infoPlist": {
"NSCameraUsageDescription": "We need camera access for photos",
"NSLocationWhenInUseUsageDescription": "Location for nearby features"
}
},
"android": {
"package": "com.company.myapp",
"versionCode": 1,
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"permissions": ["CAMERA", "ACCESS_FINE_LOCATION"]
},
"web": {
"favicon": "./assets/favicon.png",
"bundler": "metro"
},
"plugins": [
"expo-router",
[
"expo-camera",
{
"cameraPermission": "Allow $(PRODUCT_NAME) to access camera"
}
]
],
"extra": {
"apiUrl": "https://api.example.com"
}
}
}Dynamic Configuration (app.config.js)
动态配置(app.config.js)
javascript
export default ({ config }) => {
const isProduction = process.env.APP_ENV === 'production';
return {
...config,
name: isProduction ? 'My App' : 'My App (Dev)',
slug: 'my-app',
extra: {
apiUrl: isProduction ? 'https://api.production.com' : 'https://api.staging.com',
...config.extra,
},
ios: {
...config.ios,
bundleIdentifier: isProduction ? 'com.company.myapp' : 'com.company.myapp.dev',
},
android: {
...config.android,
package: isProduction ? 'com.company.myapp' : 'com.company.myapp.dev',
},
};
};javascript
export default ({ config }) => {
const isProduction = process.env.APP_ENV === 'production';
return {
...config,
name: isProduction ? 'My App' : 'My App (Dev)',
slug: 'my-app',
extra: {
apiUrl: isProduction ? 'https://api.production.com' : 'https://api.staging.com',
...config.extra,
},
ios: {
...config.ios,
bundleIdentifier: isProduction ? 'com.company.myapp' : 'com.company.myapp.dev',
},
android: {
...config.android,
package: isProduction ? 'com.company.myapp' : 'com.company.myapp.dev',
},
};
};Environment-Specific Configuration
环境专属配置
javascript
// app.config.js
const getEnvironment = () => {
if (process.env.APP_ENV === 'production') {
return {
apiUrl: 'https://api.prod.com',
sentryDsn: 'https://prod-sentry-dsn',
};
}
return {
apiUrl: 'https://api.dev.com',
sentryDsn: 'https://dev-sentry-dsn',
};
};
export default {
expo: {
extra: getEnvironment(),
},
};Access in app:
typescript
import Constants from 'expo-constants';
const apiUrl = Constants.expoConfig?.extra?.apiUrl;javascript
// app.config.js
const getEnvironment = () => {
if (process.env.APP_ENV === 'production') {
return {
apiUrl: 'https://api.prod.com',
sentryDsn: 'https://prod-sentry-dsn',
};
}
return {
apiUrl: 'https://api.dev.com',
sentryDsn: 'https://dev-sentry-dsn',
};
};
export default {
expo: {
extra: getEnvironment(),
},
};在应用中访问:
typescript
import Constants from 'expo-constants';
const apiUrl = Constants.expoConfig?.extra?.apiUrl;Best Practices
最佳实践
Performance Optimization
性能优化
- Use instead of React Native
expo-imagefor better performanceImage - Enable Hermes for Android:
"jsEngine": "hermes" - Use for smooth animations
react-native-reanimated - Lazy load screens with
React.lazy()
- 使用替代React Native的
expo-image组件以获得更好的性能Image - 为Android启用Hermes:
"jsEngine": "hermes" - 使用实现流畅动画
react-native-reanimated - 用懒加载页面
React.lazy()
Code Splitting
代码拆分
typescript
import { lazy, Suspense } from 'react';
const ProfileScreen = lazy(() => import('./screens/Profile'));
function App() {
return (
<Suspense fallback={<LoadingScreen />}>
<ProfileScreen />
</Suspense>
);
}typescript
import { lazy, Suspense } from 'react';
const ProfileScreen = lazy(() => import('./screens/Profile'));
function App() {
return (
<Suspense fallback={<LoadingScreen />}>
<ProfileScreen />
</Suspense>
);
}Error Boundaries
错误边界
typescript
import * as Sentry from '@sentry/react-native';
Sentry.init({
dsn: 'your-sentry-dsn',
environment: __DEV__ ? 'development' : 'production',
});
export default Sentry.wrap(App);typescript
import * as Sentry from '@sentry/react-native';
Sentry.init({
dsn: 'your-sentry-dsn',
environment: __DEV__ ? 'development' : 'production',
});
export default Sentry.wrap(App);Expo Doctor
Expo Doctor
Run before building:
bash
npx expo-doctorThis checks for common issues with dependencies and configuration.
</instructions>
<examples>
Example usage:
```
User: "Review this code for expo framework rule compliance"
Agent: [Analyzes code against guidelines and provides specific feedback]
```
</examples>构建前运行:
bash
npx expo-doctor该命令会检查依赖和配置中的常见问题。
</instructions>
<examples>
使用示例:
```
用户:"检查这段代码是否符合Expo Framework规范"
助手:[根据指南分析代码并提供具体反馈]
```
</examples>Memory Protocol (MANDATORY)
内存协议(必填)
Before starting:
bash
cat .claude/context/memory/learnings.mdAfter completing: Record any new patterns or exceptions discovered.
ASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.
开始前:
bash
cat .claude/context/memory/learnings.md完成后: 记录发现的任何新模式或例外情况。
假设中断:你的上下文可能会重置。如果未存储在内存中,则视为未发生。