React Native + Expo + EAS 开发Android&IOS流程完整指南,包含热更新、EAS构建、数据推送等

目录

  1. 项目概述
  2. 环境准备
  3. 项目结构
  4. 开发配置
  5. 热更新系统
  6. 构建和发布
  7. 多环境管理
  8. 最佳实践
  9. 故障排除

项目概述

本项目是一个基于 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 开发的完整流程,重点介绍了:

  1. 项目配置: 详细的配置文件说明
  2. 热更新系统: 完整的热更新实现方案
  3. 构建发布: EAS Build 和 Update 的使用
  4. 多环境管理: 环境变量和配置管理
  5. 最佳实践: 代码组织和性能优化
  6. 故障排除: 常见问题的解决方案

通过遵循这个指南,您可以构建一个稳定、可维护的跨平台移动应用,并实现高效的热更新和发布流程。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐