React Native + Expo + EAS 开发Android&IOS流程完整指南
·
React Native + Expo + EAS 开发Android&IOS流程完整指南,包含热更新、EAS构建、数据推送等
目录
项目概述
本项目是一个基于 React Native + Expo + EAS 的跨平台移动应用,主要特点:
- 技术栈: React Native + Expo SDK 50+
- 路由: Expo Router (基于文件系统的路由)
- 构建: EAS Build (Expo Application Services)
- 更新: EAS Update (热更新系统)
- 多环境: 支持 development、test、uat、production 多环境
- 平台: 支持 iOS 和 Android
环境准备
1. 系统要求
# Node.js 版本
node --version # 推荐 v18.x 或更高
# npm 版本
npm --version # 推荐 v9.x 或更高
# 操作系统
- macOS (iOS 开发必需)
- Windows/Linux (Android 开发)
2. 安装依赖
# 安装 Expo CLI
npm install -g @expo/cli
# 安装 EAS CLI
npm install -g eas-cli
# 验证安装
expo --version
eas --version
3. 项目初始化
# 创建新项目
npx create-expo-app@latest MyApp --template tabs
# 或使用现有项目
cd your-project-directory
npm install
项目结构
MyApp/
├── app/ # Expo Router 页面
│ ├── (tabs)/ # 标签页路由组
│ │ ├── _layout.tsx # 标签页布局
│ │ ├── index.tsx # 首页
│ │ └── profile.tsx # 个人中心
│ ├── _layout.tsx # 根布局
│ └── +not-found.tsx # 404页面
├── components/ # 可复用组件
│ ├── ui/ # UI组件
│ └── business/ # 业务组件
├── services/ # API服务层
├── hooks/ # 自定义Hooks
├── utils/ # 工具函数
├── types/ # TypeScript类型定义
├── constants/ # 常量定义
├── assets/ # 静态资源
├── scripts/ # 构建脚本
├── docs/ # 项目文档
├── app.json # Expo配置
├── eas.json # EAS构建配置
├── package.json # 项目依赖
└── tsconfig.json # TypeScript配置
开发配置
1. app.json 配置
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"version": "1.0.0",
"orientation": "portrait",
"userInterfaceStyle": "automatic",
"newArchEnabled": false,
"scheme": "myapp",
"platforms": ["ios", "android"],
"updates": {
"enabled": true,
"checkAutomatically": "ON_LOAD",
"fallbackToCacheTimeout": 0,
"url": "https://u.expo.dev/your-project-id"
},
"runtimeVersion": {
"policy": "appVersion"
},
"extra": {
"APP_ENV": "development",
"API_BASE_URL": "https://api.example.com",
"eas": {
"projectId": "your-project-id"
}
}
}
}
2. eas.json 配置
{
"cli": {
"version": ">= 5.9.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"env": {
"APP_ENV": "development",
"API_BASE_URL": "https://api-dev.example.com"
}
},
"preview": {
"android": {
"buildType": "apk"
},
"ios": {
"credentialsSource": "remote"
},
"env": {
"APP_ENV": "preview",
"API_BASE_URL": "https://api-preview.example.com"
}
},
"production": {
"android": {
"buildType": "aab"
},
"ios": {
"credentialsSource": "remote"
},
"env": {
"APP_ENV": "production",
"API_BASE_URL": "https://api.example.com"
}
}
},
"submit": {
"production": {
"android": {
"serviceAccountKeyPath": "./google-play-service-account.json",
"track": "internal"
},
"ios": {
"appleId": "your-apple-id@example.com",
"ascAppId": "1234567890",
"appleTeamId": "ABCD123456"
}
}
}
}
3. package.json 脚本
{
"scripts": {
"start": "expo start",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"lint": "expo lint",
"build:development": "eas build --platform all --profile development",
"build:preview": "eas build --platform all --profile preview",
"build:production": "eas build --platform all --profile production",
"update:development": "eas update --branch development",
"update:preview": "eas update --branch preview",
"update:production": "eas update --branch production",
"submit:android": "eas submit --platform android --profile production",
"submit:ios": "eas submit --platform ios --profile production"
}
}
热更新系统
1. 热更新服务实现
创建 services/updateService.ts:
import Constants from 'expo-constants';
import * as Updates from 'expo-updates';
import { Alert, Platform } from 'react-native';
export interface UpdateInfo {
isAvailable: boolean;
manifest?: Updates.Manifest;
isEmbeddedLaunch?: boolean;
isEmergencyLaunch?: boolean;
}
export interface UpdateCheckResult {
isUpdateAvailable: boolean;
updateInfo?: UpdateInfo;
error?: string;
}
class UpdateService {
private isChecking = false;
private updateCheckInterval: NodeJS.Timeout | null = null;
private translateFunction: ((key: string) => string) | null = null;
private autoCheckEnabled = true;
// 获取当前环境
private getCurrentEnvironment(): string {
return Constants.expoConfig?.extra?.APP_ENV || 'development';
}
// 获取当前通道
private getCurrentChannel(): string {
const env = this.getCurrentEnvironment();
const channelMap: Record<string, string> = {
'development': 'development',
'preview': 'preview',
'production': 'production'
};
return channelMap[env] || 'development';
}
// 检查是否有可用更新
async checkForUpdate(forceCheck: boolean = false): Promise<UpdateCheckResult> {
try {
if (this.isChecking) {
return { isUpdateAvailable: false };
}
this.isChecking = true;
const currentEnv = this.getCurrentEnvironment();
const currentChannel = this.getCurrentChannel();
console.log('Checking for updates...', {
environment: currentEnv,
channel: currentChannel,
isDev: __DEV__,
forceCheck
});
// 检查是否在开发环境中且未强制检查
if (__DEV__ && !forceCheck) {
console.log('Running in development mode, skipping update check');
return { isUpdateAvailable: false };
}
// 检查是否有可用更新
const update = await Updates.checkForUpdateAsync();
console.log('Update check result:', {
isAvailable: update.isAvailable,
environment: currentEnv,
channel: currentChannel,
currentUpdateId: Updates.updateId,
currentChannel: Updates.channel,
runtimeVersion: Updates.runtimeVersion
});
return {
isUpdateAvailable: update.isAvailable,
updateInfo: {
isAvailable: update.isAvailable,
manifest: update.manifest,
isEmbeddedLaunch: Updates.isEmbeddedLaunch,
isEmergencyLaunch: Updates.isEmergencyLaunch,
},
};
} catch (error) {
console.error('Error checking for updates:', error);
return {
isUpdateAvailable: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
} finally {
this.isChecking = false;
}
}
// 下载并安装更新
async downloadAndInstallUpdate(): Promise<boolean> {
try {
console.log('Downloading update...');
const update = await Updates.fetchUpdateAsync();
if (update.isNew) {
console.log('Update downloaded successfully, restarting app...');
await Updates.reloadAsync();
return true;
} else {
console.log('No new update available');
return false;
}
} catch (error) {
console.error('Error downloading update:', error);
return false;
}
}
// 获取当前更新信息
getCurrentUpdateInfo(): UpdateInfo {
return {
isAvailable: false,
isEmbeddedLaunch: Updates.isEmbeddedLaunch,
isEmergencyLaunch: Updates.isEmergencyLaunch,
manifest: Updates.manifest as Updates.Manifest | undefined,
};
}
// 获取环境信息
getEnvironmentInfo() {
return {
currentEnvironment: this.getCurrentEnvironment(),
expectedChannel: this.getCurrentChannel(),
actualChannel: Updates.channel,
isDev: __DEV__,
updateId: Updates.updateId,
runtimeVersion: Updates.runtimeVersion,
};
}
// 初始化更新服务
initialize() {
console.log('Initializing update service...', {
environment: this.getCurrentEnvironment(),
expectedChannel: this.getCurrentChannel(),
actualChannel: Updates.channel,
isDev: __DEV__
});
// 启动自动检查
if (this.shouldAutoCheckUpdate()) {
this.startPeriodicUpdateCheck();
}
}
private shouldAutoCheckUpdate(): boolean {
if (__DEV__) return false;
const env = this.getCurrentEnvironment();
return ['preview', 'production'].includes(env);
}
private startPeriodicUpdateCheck(intervalMs: number = 5 * 60 * 1000): void {
if (this.updateCheckInterval) {
clearInterval(this.updateCheckInterval);
}
this.updateCheckInterval = setInterval(async () => {
const result = await this.checkForUpdate();
if (result.isUpdateAvailable) {
console.log('Update available, showing notification');
this.showUpdateNotification();
}
}, intervalMs) as any;
}
private showUpdateNotification(): void {
Alert.alert(
'发现新版本',
'检测到应用有新版本可用,是否立即更新?',
[
{ text: '稍后', style: 'cancel' },
{ text: '立即更新', onPress: () => this.downloadAndInstallUpdate() },
]
);
}
}
const updateService = new UpdateService();
export default updateService;
2. 热更新集成到应用
在应用根组件中初始化:
// app/_layout.tsx
import { useEffect } from 'react';
import updateService from '@/services/updateService';
export default function RootLayout() {
useEffect(() => {
// 初始化更新服务
updateService.initialize();
}, []);
return (
// 你的应用布局
);
}
3. 手动检查更新
// components/UpdateChecker.tsx
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, Alert } from 'react-native';
import updateService from '@/services/updateService';
export default function UpdateChecker() {
const [isChecking, setIsChecking] = useState(false);
const handleCheckUpdate = async () => {
setIsChecking(true);
try {
const result = await updateService.checkForUpdate(true);
if (result.isUpdateAvailable) {
Alert.alert(
'发现新版本',
'检测到应用有新版本可用,是否立即更新?',
[
{ text: '稍后', style: 'cancel' },
{ text: '立即更新', onPress: () => updateService.downloadAndInstallUpdate() },
]
);
} else {
Alert.alert('提示', '当前已是最新版本');
}
} catch (error) {
Alert.alert('错误', '检查更新失败');
} finally {
setIsChecking(false);
}
};
return (
<TouchableOpacity onPress={handleCheckUpdate} disabled={isChecking}>
<Text>{isChecking ? '检查中...' : '检查更新'}</Text>
</TouchableOpacity>
);
}
构建和发布
1. EAS Build 配置
初始化 EAS
# 登录 EAS
eas login
# 初始化项目
eas build:configure
# 查看构建配置
eas build:list
构建命令
# 构建开发版本
eas build --platform all --profile development
# 构建预览版本
eas build --platform all --profile preview
# 构建生产版本
eas build --platform all --profile production
# 仅构建 Android
eas build --platform android --profile production
# 仅构建 iOS
eas build --platform ios --profile production
2. 热更新发布
发布到不同环境
# 发布到开发环境
eas update --branch development --message "开发环境更新"
# 发布到预览环境
eas update --branch preview --message "预览环境更新"
# 发布到生产环境
eas update --branch production --message "生产环境更新"
# 发布到特定平台
eas update --branch production --platform ios --message "iOS生产环境更新"
创建发布脚本
创建 scripts/publish-update.js:
#!/usr/bin/env node
const { execSync } = require('child_process');
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const environments = {
development: {
branch: 'development',
description: '开发环境热更新'
},
preview: {
branch: 'preview',
description: '预览环境热更新'
},
production: {
branch: 'production',
description: '生产环境热更新'
}
};
function question(query) {
return new Promise(resolve => rl.question(query, resolve));
}
async function main() {
console.log('🚀 热更新发布工具\n');
console.log('可用环境:');
Object.entries(environments).forEach(([key, env], index) => {
console.log(`${index + 1}. ${key} (${env.branch}) - ${env.description}`);
});
const envChoice = await question('\n请选择环境 (1-3): ');
const envKeys = Object.keys(environments);
const selectedEnv = envKeys[parseInt(envChoice) - 1];
if (!selectedEnv) {
console.error('❌ 无效的环境选择');
process.exit(1);
}
const env = environments[selectedEnv];
console.log(`\n✅ 已选择环境: ${selectedEnv} (${env.branch})`);
const message = await question('请输入更新消息: ');
if (!message.trim()) {
console.error('❌ 更新消息不能为空');
process.exit(1);
}
const confirm = await question(`\n确认发布到 ${selectedEnv} 环境? (y/N): `);
if (confirm.toLowerCase() !== 'y') {
console.log('❌ 已取消发布');
process.exit(0);
}
try {
console.log(`\n🔄 正在发布热更新到 ${selectedEnv} 环境...`);
const command = `npx eas update --branch ${env.branch} --message "${message}"`;
console.log(`执行命令: ${command}`);
execSync(command, {
stdio: 'inherit',
cwd: process.cwd()
});
console.log(`\n✅ 热更新已成功发布到 ${selectedEnv} 环境!`);
console.log(`📱 应用将在下次启动时自动检查并下载更新`);
} catch (error) {
console.error(`\n❌ 发布失败:`, error.message);
process.exit(1);
} finally {
rl.close();
}
}
main().catch(console.error);
3. 应用商店发布
Android (Google Play)
# 构建 AAB 文件
eas build --platform android --profile production
# 提交到 Google Play
eas submit --platform android --profile production
iOS (App Store)
# 构建 iOS 应用
eas build --platform ios --profile production
# 提交到 App Store
eas submit --platform ios --profile production
多环境管理
1. 环境变量配置
在 eas.json 中配置不同环境的变量:
{
"build": {
"development": {
"env": {
"APP_ENV": "development",
"API_BASE_URL": "https://api-dev.example.com",
"DEBUG_MODE": "true"
}
},
"staging": {
"env": {
"APP_ENV": "staging",
"API_BASE_URL": "https://api-staging.example.com",
"DEBUG_MODE": "false"
}
},
"production": {
"env": {
"APP_ENV": "production",
"API_BASE_URL": "https://api.example.com",
"DEBUG_MODE": "false"
}
}
}
}
2. 环境检测服务
// services/configService.ts
import Constants from 'expo-constants';
class ConfigService {
private config: any;
constructor() {
this.config = Constants.expoConfig?.extra || {};
}
getApiBaseUrl(): string {
return this.config.API_BASE_URL || 'https://api.example.com';
}
getAppEnvironment(): string {
return this.config.APP_ENV || 'development';
}
isDebugMode(): boolean {
return this.config.DEBUG_MODE === 'true' || __DEV__;
}
getConfig() {
return this.config;
}
}
export default new ConfigService();
3. API 服务配置
// services/apiService.ts
import configService from './configService';
class ApiService {
private baseUrl: string;
constructor() {
this.baseUrl = configService.getApiBaseUrl();
}
async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const url = `${this.baseUrl}${endpoint}`;
const defaultOptions: RequestInit = {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
};
try {
const response = await fetch(url, { ...defaultOptions, ...options });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
}
export default new ApiService();
最佳实践
1. 代码组织
- 使用 TypeScript 进行类型安全
- 组件按功能模块组织
- 服务层分离业务逻辑
- 统一的错误处理
2. 性能优化
- 使用 React.memo 优化组件渲染
- 实现图片懒加载
- 使用 FlatList 优化长列表
- 避免不必要的重新渲染
3. 错误处理
// utils/errorHandler.ts
export class ErrorHandler {
static handle(error: any, context?: string) {
console.error(`Error in ${context || 'unknown'}:`, error);
// 发送错误到监控服务
if (!__DEV__) {
this.reportError(error, context);
}
}
private static reportError(error: any, context?: string) {
// 实现错误上报逻辑
}
}
4. 测试策略
// __tests__/updateService.test.ts
import updateService from '../services/updateService';
describe('UpdateService', () => {
it('should detect development environment', () => {
const envInfo = updateService.getEnvironmentInfo();
expect(envInfo.currentEnvironment).toBe('development');
});
it('should check for updates', async () => {
const result = await updateService.checkForUpdate();
expect(result).toHaveProperty('isUpdateAvailable');
});
});
故障排除
1. 常见问题
热更新不生效
# 检查更新状态
npx eas update:list --branch production
# 检查应用配置
npx expo config
# 清理缓存
npx expo start --clear
构建失败
# 查看构建日志
eas build:list
# 重新构建
eas build --platform all --profile production --clear-cache
环境变量问题
# 验证配置
npx expo config --type public
# 检查环境变量
echo $APP_ENV
2. 调试工具
// utils/debugUtils.ts
export const debugLog = (message: string, data?: any) => {
if (__DEV__) {
console.log(`[DEBUG] ${message}`, data);
}
};
export const debugError = (error: any, context?: string) => {
console.error(`[ERROR] ${context || 'Unknown'}:`, error);
};
3. 监控和日志
// services/logService.ts
export class LogService {
static log(message: string, level: 'info' | 'warn' | 'error' = 'info') {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
// 在生产环境中发送到日志服务
if (!__DEV__) {
this.sendToLogService(message, level);
}
}
private static sendToLogService(message: string, level: string) {
// 实现日志发送逻辑
}
}
总结
这份指南涵盖了 React Native + Expo + EAS 开发的完整流程,重点介绍了:
- 项目配置: 详细的配置文件说明
- 热更新系统: 完整的热更新实现方案
- 构建发布: EAS Build 和 Update 的使用
- 多环境管理: 环境变量和配置管理
- 最佳实践: 代码组织和性能优化
- 故障排除: 常见问题的解决方案
通过遵循这个指南,您可以构建一个稳定、可维护的跨平台移动应用,并实现高效的热更新和发布流程。
更多推荐
所有评论(0)