React Native App 自动检测版本更新完整实现指南

概述

在移动应用开发中,版本更新是一个至关重要的功能。本文基于一个实际项目,详细介绍如何在 React Native 应用中实现完整的自动版本检测和更新功能。该方案支持:

  • 自动版本检查:应用启动时和定期检查版本更新
  • 强制更新支持:支持强制更新和可选更新
  • 智能重试机制:网络失败时自动重试
  • 应用状态监听:从后台回到前台时自动检查
  • 美观的更新UI:提供友好的更新对话框
  • 跨平台支持:支持 Android 和 iOS

架构设计

整体架构图

复制代码
┌─────────────────────────────────────────────────────────┐
│                    VersionUpdateManager                  │
│              (版本更新管理器 - 顶层组件)                  │
└────────────────────┬────────────────────────────────────┘
                     │
        ┌────────────┴────────────┐
        │                         │
┌───────▼────────┐      ┌─────────▼──────────┐
│ useVersionCheck│      │ VersionUpdateDialog │
│    (Hook)      │      │   (更新对话框)       │
└───────┬────────┘      └─────────────────────┘
        │
┌───────▼────────┐
│ versionManager │
│   (工具类)     │
└───────┬────────┘
        │
┌───────▼────────┐
│ versionService │
│   (API服务)    │
└────────────────┘

核心模块说明

  1. VersionUpdateManager:顶层组件,包装整个应用
  2. useVersionCheck:自定义 Hook,提供版本检查逻辑和状态管理
  3. VersionUpdateDialog:更新对话框 UI 组件
  4. versionManager:版本管理工具类,处理版本比较、检查、下载等
  5. versionService:API 服务层,与后端通信

核心组件详解

1. VersionManager(版本管理工具类)

这是整个版本更新系统的核心,负责:

  • 获取当前应用版本
  • 版本号比较
  • 检查版本更新
  • 下载和安装更新包

关键特性:

  • 单例模式,确保全局唯一实例
  • 版本号语义化比较(支持 x.y.z 格式)
  • 支持强制更新判断逻辑

2. useVersionCheck Hook

提供版本检查的 React Hook,封装了:

  • 自动检查逻辑
  • 定时检查机制
  • 应用状态监听
  • 重试机制
  • 状态管理

关键特性:

  • 支持自定义检查间隔
  • 智能重试机制(避免频繁请求)
  • 监听应用前后台切换

3. VersionUpdateDialog 组件

更新对话框 UI 组件,展示:

  • 版本号信息
  • 更新内容说明
  • 更新包大小
  • 更新/取消按钮

关键特性:

  • 强制更新时隐藏取消按钮
  • 实时显示更新状态(检查中/下载中/安装中)
  • 响应式设计

实现步骤

步骤 1:创建版本 API 服务

首先定义版本信息接口和 API 调用方法。

步骤 2:实现版本管理工具类

创建 VersionManager 单例类,实现版本比较和更新检查逻辑。

步骤 3:开发版本检查 Hook

创建 useVersionCheck Hook,封装版本检查的状态管理和逻辑。

步骤 4:构建更新对话框组件

创建 VersionUpdateDialog 组件,提供友好的更新提示界面。

步骤 5:集成版本更新管理器

在应用入口处集成 VersionUpdateManager 组件。


核心代码实现

1. 版本 API 服务(versionService.ts)

typescript 复制代码
// 使用 axios 或其他 HTTP 客户端
import axios from 'axios';
// 或者使用 fetch API
// const API_BASE_URL = 'https://your-api-server.com';

// 版本信息接口
export interface VersionInfo {
  version: string;
  minVersion: string;
  downloadUrl: string;
  releaseNotes: string;
  forceUpdate: boolean;
  updateSize?: string;
  updateTime?: string;
}

// 版本检查响应接口
export interface VersionCheckResponse {
  code: number;
  data: VersionInfo;
  message?: string;
  error?: string;
}

// 当前应用版本信息
export interface CurrentVersionInfo {
  version: string;
  platform: 'android' | 'ios';
  appType?: string; // 可选的应用类型,根据实际需求定义
}

// 版本管理服务
export const versionService = {
  // 获取版本信息(合并了检查更新和获取最新版本的功能)
  getVersionInfo: async (currentVersion: CurrentVersionInfo): Promise<VersionCheckResponse> => {
    // 使用 axios(需要配置 baseURL)
    const API_BASE_URL = 'https://your-api-server.com'; // 替换为实际的API地址
    const response = await axios.post<VersionCheckResponse>(
      `${API_BASE_URL}/api/version/info`,
      currentVersion
    );
    return response.data;
  
    // 或者使用 fetch
    // const API_BASE_URL = 'https://your-api-server.com';
    // const response = await fetch(`${API_BASE_URL}/api/version/info`, {
    //   method: 'POST',
    //   headers: { 'Content-Type': 'application/json' },
    //   body: JSON.stringify(currentVersion),
    // });
    // const data = await response.json();
    // return data;
  },
};

说明:

  • VersionInfo:服务器返回的最新版本信息
  • CurrentVersionInfo:当前应用的基本信息(版本号、平台、应用类型)
  • getVersionInfo:调用后端 API 获取版本信息

2. 版本管理工具类(versionManager.ts)

typescript 复制代码
import { Platform, Linking } from 'react-native';
import { versionService, VersionInfo, CurrentVersionInfo } from '../api/versionService';
// 根据项目需求,可以导入应用配置或使用默认值
// import { APP_CONFIG } from '../config/appConfig';

// 版本比较结果
export type VersionCompareResult = 'newer' | 'same' | 'older';

// 版本更新状态
export type UpdateStatus =
  | 'checking'
  | 'available'
  | 'upToDate'
  | 'downloading'
  | 'installing'
  | 'completed'
  | 'error';

// 版本管理器
export class VersionManager {
  private static instance: VersionManager;
  private updateStatus: UpdateStatus = 'checking';
  private currentVersion: CurrentVersionInfo | null = null;
  private latestVersion: VersionInfo | null = null;

  private constructor() {}

  public static getInstance(): VersionManager {
    if (!VersionManager.instance) {
      VersionManager.instance = new VersionManager();
    }
    return VersionManager.instance;
  }

  /**
   * 获取当前应用版本信息
   */
  public getCurrentVersion(): CurrentVersionInfo {
    if (this.currentVersion) {
      return this.currentVersion;
    }

    // 从package.json动态获取版本信息
    let packageVersion = '0.0.1'; // 默认版本
    try {
      // 读取package.json的版本号
      const packageJson = require('../../package.json');
      packageVersion = packageJson.version || '0.0.1';
    } catch (error) {
      console.warn('无法读取package.json版本信息,使用默认版本:', error);
    }

    this.currentVersion = {
      version: packageVersion,
      platform: Platform.OS as 'android' | 'ios',
      // 根据实际需求设置应用类型,或从配置文件读取
      // appType: APP_CONFIG.appType || 'mobile',
    };

    return this.currentVersion;
  }

  /**
   * 比较版本号
   * @param version1 版本1
   * @param version2 版本2
   * @returns 比较结果
   */
  public compareVersions(version1: string, version2: string): VersionCompareResult {
    const v1Parts = version1.split('.').map(Number);
    const v2Parts = version2.split('.').map(Number);

    // 补齐版本号位数
    const maxLength = Math.max(v1Parts.length, v2Parts.length);
    while (v1Parts.length < maxLength) v1Parts.push(0);
    while (v2Parts.length < maxLength) v2Parts.push(0);

    for (let i = 0; i < maxLength; i++) {
      if (v1Parts[i] > v2Parts[i]) return 'newer';
      if (v1Parts[i] < v2Parts[i]) return 'older';
    }

    return 'same';
  }

  /**
   * 检查版本更新
   */
  public async checkForUpdate(): Promise<{
    hasUpdate: boolean;
    versionInfo?: VersionInfo;
    isForceUpdate: boolean;
  }> {
    try {
      this.updateStatus = 'checking';

      const currentVersion = this.getCurrentVersion();
      const response = await versionService.getVersionInfo(currentVersion);

      // 检查业务状态码:支持 0 或 200 都视为成功
      const isSuccess = response.code === 0 || response.code === 200;

      if (!isSuccess) {
        console.error('[版本检查] API返回错误:', response.message || response.error);
        return { hasUpdate: false, versionInfo: undefined, isForceUpdate: false };
      }

      if (!response.data) {
        console.error('[版本检查] API响应中没有data字段');
        return { hasUpdate: false, versionInfo: undefined, isForceUpdate: false };
      }

      const latestVersion = response.data;

      // 比较当前版本与最新版本
      const isOlderThanLatest =
        this.compareVersions(currentVersion.version, latestVersion.version) === 'older';

      // 比较当前版本与最小版本(如果当前版本小于minVersion,则需要更新)
      const isOlderThanMinVersion =
        this.compareVersions(currentVersion.version, latestVersion.minVersion) === 'older';

      // 判断是否需要更新:当前版本小于最新版本 OR 当前版本小于最小版本
      const hasUpdate = isOlderThanLatest || isOlderThanMinVersion;

      // 判断是否需要强制更新:
      // 1. 接口返回的forceUpdate为true
      // 2. 当前版本小于最小版本(必须强制更新)
      const isForceUpdate = latestVersion.forceUpdate || isOlderThanMinVersion;

      console.log(
        `[版本检查] 当前: ${currentVersion.version}, 最新: ${latestVersion.version}, 最小: ${latestVersion.minVersion}, 需要更新: ${hasUpdate ? '是' : '否'}, 强制更新: ${isForceUpdate ? '是' : '否'}`
      );

      if (hasUpdate) {
        this.latestVersion = latestVersion;
        this.updateStatus = 'available';
        return {
          hasUpdate: true,
          versionInfo: latestVersion,
          isForceUpdate,
        };
      } else {
        this.updateStatus = 'upToDate';
        return {
          hasUpdate: false,
          versionInfo: undefined,
          isForceUpdate: false,
        };
      }
    } catch (error: any) {
      console.error('版本检查失败:', error);
      this.updateStatus = 'error';
      return { hasUpdate: false, versionInfo: undefined, isForceUpdate: false };
    }
  }

  /**
   * 下载并安装更新
   */
  public async downloadAndInstall(downloadUrl: string): Promise<void> {
    try {
      this.updateStatus = 'downloading';

      // 在Android上,直接打开下载链接
      if (Platform.OS === 'android') {
        const canOpen = await Linking.canOpenURL(downloadUrl);
        if (canOpen) {
          await Linking.openURL(downloadUrl);
          this.updateStatus = 'installing';
        } else {
          throw new Error('无法打开下载链接');
        }
      } else {
        // iOS通过App Store更新
        const canOpen = await Linking.canOpenURL(downloadUrl);
        if (canOpen) {
          await Linking.openURL(downloadUrl);
        } else {
          throw new Error('无法打开App Store');
        }
      }
    } catch (error) {
      console.error('下载安装失败:', error);
      this.updateStatus = 'error';
      throw error;
    }
  }

  /**
   * 获取更新状态
   */
  public getUpdateStatus(): UpdateStatus {
    return this.updateStatus;
  }

  /**
   * 获取最新版本信息
   */
  public getLatestVersion(): VersionInfo | null {
    return this.latestVersion;
  }

  /**
   * 重置更新状态
   */
  public resetUpdateStatus(): void {
    this.updateStatus = 'checking';
  }

  /**
   * 检查是否为强制更新
   */
  public isForceUpdate(): boolean {
    return this.latestVersion?.forceUpdate || false;
  }

  /**
   * 清除当前版本缓存,重新读取版本信息
   */
  public resetCurrentVersion(): void {
    this.currentVersion = null;
  }
}

// 导出单例实例
export const versionManager = VersionManager.getInstance();

核心方法说明:

  1. getCurrentVersion() :从 package.json 读取当前版本号,结合平台和应用类型构建版本信息
  2. compareVersions() :语义化版本比较,支持 x.y.z 格式,返回 newersameolder
  3. checkForUpdate()
    • 调用 API 获取最新版本信息
    • 比较当前版本与最新版本、最小版本
    • 判断是否需要更新和是否强制更新
  4. downloadAndInstall() :使用 Linking API 打开下载链接(Android)或 App Store(iOS)

3. 版本检查 Hook(useVersionCheck.ts)

typescript 复制代码
import { useState, useEffect, useCallback, useRef } from 'react';
import { AppState, AppStateStatus } from 'react-native';
import { versionManager, UpdateStatus } from '../utils/versionManager';
import { VersionInfo } from '../api/versionService';

interface UseVersionCheckOptions {
  autoCheck?: boolean;
  checkInterval?: number; // 检查间隔(毫秒)
  maxRetries?: number; // 最大重试次数
  retryDelay?: number; // 重试延迟(毫秒)
  onUpdateAvailable?: (versionInfo: VersionInfo) => void;
  onUpdateRequired?: (versionInfo: VersionInfo) => void;
}

interface UseVersionCheckReturn {
  isChecking: boolean;
  hasUpdate: boolean;
  latestVersion: VersionInfo | null;
  updateStatus: UpdateStatus;
  checkForUpdate: () => Promise<void>;
  showUpdateDialog: () => void;
  hideUpdateDialog: () => void;
  isUpdateDialogVisible: boolean;
  isForceUpdate: boolean;
}

export const useVersionCheck = (options: UseVersionCheckOptions = {}): UseVersionCheckReturn => {
  const {
    autoCheck = true,
    checkInterval = 5 * 60 * 1000, // 默认5分钟检查一次
    maxRetries = 3, // 默认最大重试3次
    retryDelay = 30000, // 默认重试延迟30秒
    onUpdateAvailable,
    onUpdateRequired,
  } = options;

  const [isChecking, setIsChecking] = useState(false);
  const [hasUpdate, setHasUpdate] = useState(false);
  const [latestVersion, setLatestVersion] = useState<VersionInfo | null>(null);
  const [updateStatus, setUpdateStatus] = useState<UpdateStatus>('checking');
  const [isUpdateDialogVisible, setIsUpdateDialogVisible] = useState(false);
  const [isForceUpdate, setIsForceUpdate] = useState(false);

  // 使用ref存储重试计数和上次错误时间,避免重新渲染
  const retryCountRef = useRef(0);
  const lastErrorTimeRef = useRef(0);
  const lastCheckTimeRef = useRef(0);
  const checkForUpdateRef = useRef<(() => Promise<void>) | undefined>(undefined);

  // 检查更新
  const checkForUpdate = useCallback(async () => {
    if (isChecking) return;

    // 检查距离上次错误的时间,避免频繁重试
    const now = Date.now();
    const timeSinceLastError = now - lastErrorTimeRef.current;

    if (lastErrorTimeRef.current > 0 && timeSinceLastError < retryDelay) {
      return;
    }

    // 检查是否超过最大重试次数
    if (retryCountRef.current >= maxRetries) {
      return;
    }

    // 记录本次检查时间
    lastCheckTimeRef.current = now;

    try {
      setIsChecking(true);
      setUpdateStatus('checking');

      const result = await versionManager.checkForUpdate();

      // 检查成功,重置重试计数
      retryCountRef.current = 0;
      lastErrorTimeRef.current = 0;

      if (result.hasUpdate && result.versionInfo) {
        setHasUpdate(true);
        setLatestVersion(result.versionInfo);
        setIsForceUpdate(result.isForceUpdate);

        // 触发回调
        if (result.isForceUpdate) {
          onUpdateRequired?.(result.versionInfo);
          setIsUpdateDialogVisible(true);
        } else {
          onUpdateAvailable?.(result.versionInfo);
        }
      } else {
        setHasUpdate(false);
        setLatestVersion(null);
        setIsForceUpdate(false);
      }
    } catch (error) {
      console.error('[版本检查] 失败:', error);
      setUpdateStatus('error');

      // 增加重试计数
      retryCountRef.current += 1;
      lastErrorTimeRef.current = Date.now();
    } finally {
      setIsChecking(false);
    }
  }, [isChecking, maxRetries, retryDelay, onUpdateAvailable, onUpdateRequired]);

  // 更新ref
  checkForUpdateRef.current = checkForUpdate;

  // 显示更新对话框
  const showUpdateDialog = useCallback(() => {
    if (hasUpdate && latestVersion) {
      setIsUpdateDialogVisible(true);
    }
  }, [hasUpdate, latestVersion]);

  // 隐藏更新对话框
  const hideUpdateDialog = useCallback(() => {
    if (!isForceUpdate) {
      setIsUpdateDialogVisible(false);
    }
  }, [isForceUpdate]);

  // 监听应用状态变化
  useEffect(() => {
    if (!autoCheck) return;

    const handleAppStateChange = (nextAppState: AppStateStatus) => {
      if (nextAppState === 'active') {
        // 应用从后台回到前台时检查更新
        const now = Date.now();
        const timeSinceLastCheck = now - lastCheckTimeRef.current;

        // 如果距离上次检查超过1分钟,才检查更新
        if (timeSinceLastCheck > 60000) {
          checkForUpdateRef.current?.();
        }
      }
    };

    const subscription = AppState.addEventListener('change', handleAppStateChange);
    return () => subscription?.remove();
  }, [autoCheck]);

  // 定时检查更新
  useEffect(() => {
    if (!autoCheck) return;

    // 立即检查一次
    if (checkForUpdateRef.current) {
      checkForUpdateRef.current();
    } else {
      // 延迟一下,等checkForUpdateRef被赋值
      setTimeout(() => {
        checkForUpdateRef.current?.();
      }, 100);
    }

    // 设置定时器
    const interval = setInterval(() => {
      checkForUpdateRef.current?.();
    }, checkInterval);

    return () => clearInterval(interval);
  }, [autoCheck, checkInterval]);

  // 监听更新状态变化
  useEffect(() => {
    const interval = setInterval(() => {
      const currentStatus = versionManager.getUpdateStatus();
      setUpdateStatus(currentStatus);
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  return {
    isChecking,
    hasUpdate,
    latestVersion,
    updateStatus,
    checkForUpdate,
    showUpdateDialog,
    hideUpdateDialog,
    isUpdateDialogVisible,
    isForceUpdate,
  };
};

核心功能说明:

  1. 自动检查机制

    • 应用启动时立即检查一次
    • 按设定的时间间隔定期检查
    • 应用从后台回到前台时检查(距离上次检查超过1分钟)
  2. 智能重试机制

    • 网络失败时自动重试
    • 限制重试次数和重试间隔,避免频繁请求
    • 使用 ref 存储重试状态,避免不必要的重新渲染
  3. 状态管理

    • 管理检查状态、更新状态、对话框显示状态
    • 实时同步 versionManager 的更新状态

4. 更新对话框组件(VersionUpdateDialog.tsx)

typescript 复制代码
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
import { versionManager, UpdateStatus } from '../utils/versionManager';
import { VersionInfo } from '../api/versionService';

interface VersionUpdateDialogProps {
  visible: boolean;
  versionInfo: VersionInfo;
  onUpdate: () => void;
  onCancel: () => void;
}

export const VersionUpdateDialog: React.FC<VersionUpdateDialogProps> = ({
  visible,
  versionInfo,
  onUpdate,
  onCancel,
}) => {
  const [updateStatus, setUpdateStatus] = useState<UpdateStatus>('checking');

  useEffect(() => {
    if (visible) {
      setUpdateStatus(versionManager.getUpdateStatus());
    }
  }, [visible]);

  useEffect(() => {
    const interval = setInterval(() => {
      setUpdateStatus(versionManager.getUpdateStatus());
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  const handleUpdate = async () => {
    try {
      onUpdate();
      await versionManager.downloadAndInstall(versionInfo.downloadUrl);
    } catch (error) {
      console.error('更新失败:', error);
    }
  };

  const handleCancel = () => {
    if (versionInfo.forceUpdate) {
      return;
    }
    onCancel();
  };

  if (!visible) return null;

  return (
    <View style={styles.overlay}>
      <View style={styles.dialog}>
        {/* Header */}
        <View style={styles.header}>
          <Text style={styles.title}>发现新版本</Text>
          <Text style={styles.versionText}>v{versionInfo.version}</Text>
        </View>

        {/* Content */}
        <ScrollView style={styles.scrollContent} showsVerticalScrollIndicator={false}>
          <View style={styles.content}>
            {/* Release Notes */}
            {versionInfo.releaseNotes && (
              <View style={styles.releaseNotesContainer}>
                <Text style={styles.releaseNotesTitle}>更新内容</Text>
                <Text style={styles.releaseNotes}>{versionInfo.releaseNotes}</Text>
              </View>
            )}

            {/* Update Size */}
            {versionInfo.updateSize && (
              <Text style={styles.updateSize}>大小:{versionInfo.updateSize}</Text>
            )}
          </View>
        </ScrollView>

        {/* Footer */}
        <View style={styles.footer}>
          {!versionInfo.forceUpdate && (
            <TouchableOpacity
              onPress={handleCancel}
              style={[styles.button, styles.cancelButton]}
              disabled={updateStatus === 'downloading' || updateStatus === 'installing'}
            >
              <Text style={styles.cancelButtonText}>稍后更新</Text>
            </TouchableOpacity>
          )}

          <TouchableOpacity
            onPress={handleUpdate}
            style={[styles.button, styles.updateButton]}
            disabled={updateStatus === 'downloading' || updateStatus === 'installing'}
          >
            <Text style={styles.updateButtonText}>
              {updateStatus === 'downloading' || updateStatus === 'installing'
                ? '更新中...'
                : '立即更新'}
            </Text>
          </TouchableOpacity>
        </View>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  overlay: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    justifyContent: 'center',
    alignItems: 'center',
    zIndex: 9999,
  },
  dialog: {
    backgroundColor: '#FFFFFF',
    borderRadius: 12,
    maxWidth: 400,
    width: '85%',
    maxHeight: '70%',
  },
  header: {
    padding: 20,
    alignItems: 'center',
    borderBottomWidth: 1,
    borderBottomColor: '#F0F0F0',
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#333333',
    marginBottom: 8,
    textAlign: 'center',
  },
  versionText: {
    fontSize: 16,
    color: '#007AFF',
    fontWeight: '600',
  },
  scrollContent: {
    maxHeight: 250,
  },
  content: {
    padding: 20,
  },
  releaseNotesContainer: {
    marginBottom: 16,
  },
  releaseNotesTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#333333',
    marginBottom: 12,
  },
  releaseNotes: {
    fontSize: 14,
    color: '#666666',
    lineHeight: 22,
  },
  updateSize: {
    fontSize: 14,
    color: '#666666',
  },
  footer: {
    flexDirection: 'row',
    padding: 20,
    paddingTop: 16,
    gap: 12,
    borderTopWidth: 1,
    borderTopColor: '#F0F0F0',
  },
  button: {
    flex: 1,
    padding: 12,
    borderRadius: 8,
    alignItems: 'center',
    justifyContent: 'center',
  },
  cancelButton: {
    backgroundColor: '#F5F5F5',
    borderWidth: 1,
    borderColor: '#DDDDDD',
  },
  cancelButtonText: {
    fontSize: 16,
    color: '#333333',
  },
  updateButton: {
    backgroundColor: '#007AFF',
  },
  updateButtonText: {
    fontSize: 16,
    color: '#FFFFFF',
    fontWeight: '600',
  },
});

export default VersionUpdateDialog;

组件特性:

  1. 模态对话框:使用遮罩层和居中对话框
  2. 版本信息展示:显示版本号、更新内容、更新包大小
  3. 强制更新处理:强制更新时隐藏取消按钮
  4. 状态反馈:实时显示更新状态(检查中/下载中/安装中)
  5. 响应式设计:适配不同屏幕尺寸

5. 版本更新管理器(VersionUpdateManager.tsx)

typescript 复制代码
import React from 'react';
import { View } from 'react-native';
import { useVersionCheck } from '../hooks/useVersionCheck';
import { VersionUpdateDialog } from '../components/VersionUpdateDialog';
import { versionManager } from '../utils/versionManager';

interface VersionUpdateManagerProps {
  children: React.ReactNode;
  autoCheck?: boolean;
  checkInterval?: number;
  maxRetries?: number;
  retryDelay?: number;
}

export const VersionUpdateManager: React.FC<VersionUpdateManagerProps> = ({
  children,
  autoCheck = true,
  checkInterval = 5 * 60 * 1000, // 5分钟
  maxRetries = 3,
  retryDelay = 30000, // 30秒
}) => {
  const {
    hasUpdate,
    latestVersion,
    isUpdateDialogVisible,
    isForceUpdate,
    showUpdateDialog,
    hideUpdateDialog,
    checkForUpdate,
  } = useVersionCheck({
    autoCheck,
    checkInterval,
    maxRetries,
    retryDelay,
    onUpdateAvailable: (versionInfo) => {
      console.log('发现新版本:', versionInfo.version);
      // 可以在这里添加自定义逻辑,比如显示通知
    },
    onUpdateRequired: (versionInfo) => {
      console.log('需要强制更新:', versionInfo.version);
      // 强制更新会自动显示对话框
    },
  });

  const handleUpdate = async () => {
    if (!latestVersion) return;

    try {
      await versionManager.downloadAndInstall(latestVersion.downloadUrl);
    } catch (error) {
      console.error('更新失败:', error);
    }
  };

  const handleCancel = () => {
    hideUpdateDialog();
  };

  return (
    <View style={{ flex: 1 }}>
      {children}

      {hasUpdate && latestVersion && (
        <VersionUpdateDialog
          visible={isUpdateDialogVisible}
          versionInfo={latestVersion}
          onUpdate={handleUpdate}
          onCancel={handleCancel}
        />
      )}
    </View>
  );
};

export default VersionUpdateManager;

组件职责:

  1. 包装应用:作为顶层组件,包装整个应用
  2. 配置管理:接收配置参数(自动检查、检查间隔、重试次数等)
  3. 事件处理:处理更新和取消操作
  4. 条件渲染:只在有更新时显示对话框

使用示例

示例 1:在应用入口集成版本更新管理器

typescript 复制代码
import React from 'react';
import { View } from 'react-native';
import { VersionUpdateManager } from './components/VersionUpdateManager';

const App = (): React.JSX.Element => {
  return (
    <VersionUpdateManager autoCheck={true} checkInterval={5 * 60 * 1000}>
      {/* 你的应用内容 */}
      <AppContent />
    </VersionUpdateManager>
  );
};

export default App;

说明:

  • VersionUpdateManager 包装在应用的根组件外层
  • 设置 autoCheck={true} 启用自动检查
  • 设置 checkInterval 控制检查间隔(5分钟)

示例 2:在设置页面手动检查更新

typescript 复制代码
import React from 'react';
import { View, Text, Button } from 'react-native';
import { useVersionCheck } from './hooks/useVersionCheck';

const SettingsScreen: React.FC = () => {
  const {
    isChecking,
    hasUpdate,
    latestVersion,
    checkForUpdate,
    showUpdateDialog,
  } = useVersionCheck({
    autoCheck: false, // 不自动检查
    onUpdateAvailable: (versionInfo) => {
      console.log('发现新版本:', versionInfo.version);
      showUpdateDialog(); // 手动显示对话框
    },
  });

  const handleCheckUpdate = async () => {
    await checkForUpdate();
    if (hasUpdate) {
      showUpdateDialog();
    }
  };

  return (
    <View>
      <Text>当前版本: v1.0.0</Text>
      <Button
        title={isChecking ? '检查中...' : '检查更新'}
        onPress={handleCheckUpdate}
        disabled={isChecking}
      />
      {hasUpdate && latestVersion && (
        <Text>发现新版本: v{latestVersion.version}</Text>
      )}
    </View>
  );
};

示例 3:自定义检查逻辑

typescript 复制代码
import { useVersionCheck } from './hooks/useVersionCheck';

const CustomUpdateChecker: React.FC = () => {
  const { checkForUpdate, hasUpdate, latestVersion } = useVersionCheck({
    autoCheck: true,
    checkInterval: 10 * 60 * 1000, // 10分钟检查一次
    maxRetries: 5, // 最多重试5次
    retryDelay: 60000, // 重试延迟60秒
    onUpdateAvailable: (versionInfo) => {
      // 自定义处理逻辑
      if (versionInfo.version.includes('beta')) {
        // Beta版本不自动弹出
        console.log('发现Beta版本,不自动提示');
      } else {
        // 正式版本自动提示
        showUpdateDialog();
      }
    },
    onUpdateRequired: (versionInfo) => {
      // 强制更新处理
      Alert.alert('必须更新', '请更新到最新版本以继续使用');
    },
  });

  // 组件挂载时检查一次
  useEffect(() => {
    checkForUpdate();
  }, []);

  return null;
};

最佳实践

1. 版本号管理

  • 使用语义化版本号(Semantic Versioning):主版本号.次版本号.补丁版本号
  • package.json 中维护版本号,确保与应用版本一致
  • 使用自动化脚本同步版本号到原生代码(Android 的 build.gradle、iOS 的 Info.plist

2. 检查频率控制

  • 启动时检查:应用启动时立即检查一次
  • 定期检查:建议间隔 5-10 分钟,避免过于频繁
  • 后台唤醒检查:应用从后台回到前台时检查,但限制最小间隔(如 1 分钟)

3. 错误处理和重试

  • 实现智能重试机制,避免网络波动导致的检查失败
  • 限制重试次数和重试间隔,防止无限重试
  • 记录错误日志,便于排查问题

4. 用户体验优化

  • 强制更新:对于重大安全更新或 API 变更,使用强制更新
  • 可选更新:常规更新允许用户选择稍后更新
  • 更新提示:提供清晰的版本说明和更新内容
  • 状态反馈:实时显示更新状态(检查中/下载中/安装中)

5. 安全性考虑

  • 验证下载链接的有效性
  • 使用 HTTPS 协议传输版本信息
  • 对更新包进行签名验证(Android)
  • 防止中间人攻击(MITM)

6. 跨平台兼容性

  • Android:直接打开下载链接,引导用户安装 APK
  • iOS:跳转到 App Store 进行更新
  • 根据平台特性提供不同的更新流程

常见问题

Q1: 如何判断是否需要更新?

A: 通过比较当前版本号与服务器返回的最新版本号:

  • 如果当前版本 < 最新版本,需要更新
  • 如果当前版本 < 最小版本(minVersion),需要强制更新

Q2: 强制更新和可选更新的区别?

A:

  • 强制更新:用户无法取消,必须更新才能继续使用应用
  • 可选更新:用户可以稍后更新,应用可以继续使用

Q3: 如何避免频繁检查更新?

A:

  • 设置合理的检查间隔(建议 5-10 分钟)
  • 应用从后台回到前台时,限制最小检查间隔(如 1 分钟)
  • 实现智能重试机制,避免网络错误导致的频繁重试

Q4: iOS 应用如何更新?

A: iOS 应用必须通过 App Store 更新,不能直接下载安装包。可以将下载链接设置为 App Store 链接,使用 Linking.openURL() 打开。

Q5: 如何处理版本检查失败?

A:

  • 实现重试机制,网络失败时自动重试
  • 限制重试次数和重试间隔
  • 记录错误日志,便于排查问题
  • 失败时不影响应用正常使用

Q6: 如何测试版本更新功能?

A:

  • 在测试环境中配置不同的版本号
  • 模拟服务器返回不同版本的更新信息
  • 测试强制更新和可选更新场景
  • 测试网络错误和重试机制

总结

本文详细介绍了一个完整的 React Native 应用版本自动检测和更新方案。该方案具有以下特点:

  1. 架构清晰:采用分层架构,职责明确,易于维护
  2. 功能完整:支持自动检查、强制更新、智能重试、状态管理
  3. 用户体验好:提供友好的更新提示和状态反馈
  4. 可扩展性强:支持自定义配置和回调函数
  5. 跨平台支持:同时支持 Android 和 iOS

核心优势

  • 自动化:应用启动和定期自动检查,无需用户手动操作
  • 智能化:智能重试机制,网络失败时自动重试
  • 用户友好:清晰的更新提示和状态反馈
  • 灵活配置:支持自定义检查间隔、重试次数等参数
  • 强制更新支持:支持强制更新和可选更新两种模式

适用场景

  • 需要频繁更新应用功能的场景
  • 需要快速修复关键 Bug 的场景
  • 需要强制用户更新到特定版本的场景
  • 需要提供良好更新体验的场景

后续优化方向

  1. 增量更新:支持增量更新,减少下载包大小
  2. 下载进度:显示详细的下载进度
  3. 后台下载:支持后台下载更新包
  4. 更新统计:统计更新成功率、用户更新行为等
  5. A/B 测试:支持灰度发布和 A/B 测试

相关推荐
掘金安东尼2 小时前
前端周刊第439期(2025年11月3日–11月9日)
前端·javascript·vue.js
起这个名字3 小时前
微前端应用通信使用和原理
前端·javascript·vue.js
小p3 小时前
react学习6:受控组件
前端·react.js
鹏多多3 小时前
Web使用natapp进行内网穿透和预览本地页面
前端·javascript
钱端工程师3 小时前
uniapp封装uni.request请求,实现重复接口请求中断上次请求(防抖)
前端·javascript·uni-app
茶憶4 小时前
uni-app app移动端实现纵向滑块功能,并伴随自动播放
javascript·vue.js·uni-app·html·scss
茶憶4 小时前
uniapp移动端实现触摸滑动功能:上下滑动展开收起内容,左右滑动删除列表
前端·javascript·vue.js·uni-app
Ayn慢慢4 小时前
uni-app PDA焦点录入实现
前端·javascript·uni-app
鹏仔工作室4 小时前
vue中实现1小时不操作则退出登录功能
前端·javascript·vue.js