【跨平台】跨平台开发实战:从原生到多端
引言
跨平台开发一直是移动和桌面应用开发中的热门话题。作为一名有着丰富跨平台开发经验的工程师,我使用过PhoneGap、React Native、Flutter等多种跨平台框架,也原生开发过iOS和Android应用。每一个框架都有其优势和局限性,选择合适的框架需要综合考虑项目需求、团队技术栈、性能要求等多个因素。
本文将系统性地介绍跨平台开发的各个方面,从React Native到Flutter,从桌面Electron到小程序,我会结合实际项目经验,分析各框架的特点、适用场景和开发实践。同时,我也会分享一些跨平台开发中的最佳实践和常见问题的解决方案。
一、跨平台开发概述
1.1 跨平台框架分类
跨平台框架可以分为几大类:Web容器型、编译型、混合型以及新兴的AI辅助开发。每种类型都有其特点和适用场景。
// 跨平台框架分类 const CrossPlatformFrameworks = { // Web容器型(基于WebView) webContainer: [ 'Cordova/PhoneGap', 'Ionic', '微信小程序', '支付宝小程序' ], // 编译型(将代码编译为原生代码) compiled: [ 'Flutter', 'React Native (Hermes引擎)', 'Kotlin Multiplatform' ], // 混合型(Web+原生) hybrid: [ 'Capacitor', 'Electron', 'Tauri' ], // AI辅助开发(新兴) aiAssisted: [ 'Cursor', 'GitHub Copilot', 'Claude' ] }; // 选择框架的决策树 interface FrameworkSelectionCriteria { performance: 'high' | 'medium' | 'low'; platform: 'mobile' | 'desktop' | 'all'; teamSkill: 'web' | 'native' | 'mixed'; timeToMarket: 'fast' | 'medium' | 'slow'; } function selectFramework(criteria: FrameworkSelectionCriteria): string[] { const recommendations: string[] = []; if (criteria.platform === 'mobile') { if (criteria.performance === 'high') { recommendations.push('Flutter'); recommendations.push('Kotlin Multiplatform'); } else if (criteria.teamSkill === 'web') { recommendations.push('React Native'); } } if (criteria.platform === 'desktop') { if (criteria.performance === 'high') { recommendations.push('Tauri'); } else { recommendations.push('Electron'); } } if (criteria.timeToMarket === 'fast') { recommendations.push('Flutter'); recommendations.push('React Native'); } return recommendations; }1.2 跨平台架构设计
// 跨平台应用架构 interface User { id: string; name: string; email: string; avatar?: string; } interface Order { id: string; userId: string; items: OrderItem[]; totalAmount: number; status: 'pending' | 'paid' | 'shipped' | 'delivered'; createdAt: Date; } interface OrderItem { productId: string; productName: string; quantity: number; price: number; } // 业务逻辑层(共享) class OrderService { async createOrder(userId: string, items: OrderItem[]): Promise<Order> { // 验证用户 const user = await this.userRepository.findById(userId); if (!user) { throw new Error('User not found'); } // 验证库存 for (const item of items) { const available = await this.inventoryService.checkStock( item.productId, item.quantity ); if (!available) { throw new Error(`Insufficient stock for ${item.productName}`); } } // 计算总价 const totalAmount = items.reduce( (sum, item) => sum + item.price * item.quantity, 0 ); // 创建订单 const order: Order = { id: this.generateOrderId(), userId, items, totalAmount, status: 'pending', createdAt: new Date() }; await this.orderRepository.save(order); // 发送通知 await this.notificationService.sendOrderConfirmation(order); return order; } } // 平台特定实现 interface PlatformService { showNotification(title: string, body: string): void; getDeviceInfo(): DeviceInfo; saveToLocalStorage(key: string, value: string): void; } // Flutter实现 class FlutterPlatformService implements PlatformService { showNotification(title: string, body: string): void { // Flutter实现 } getDeviceInfo(): DeviceInfo { return { platform: 'android', version: '12', deviceId: 'xxx' }; } } // React Native实现 class ReactNativePlatformService implements PlatformService { showNotification(title: string, body: string): void { // React Native实现 } }二、React Native开发
2.1 项目结构与配置
// 项目结构 /* src/ ├── components/ # 共享组件 │ ├── Button/ │ ├── Input/ │ └── List/ ├── screens/ # 页面 │ ├── HomeScreen/ │ ├── ProfileScreen/ │ └── OrderScreen/ ├── navigation/ # 导航配置 │ ├── AppNavigator.tsx │ └── types.ts ├── services/ # 服务层 │ ├── api/ │ ├── storage/ │ └── analytics/ ├── hooks/ # 自定义Hook │ ├── useAuth.ts │ ├── useOrders.ts │ └── usePlatform.ts ├── store/ # 状态管理 │ └── index.ts ├── utils/ # 工具函数 │ ├── format.ts │ └── validation.ts ├── types/ # 类型定义 │ └── index.ts └── App.tsx */ // React Native导航配置 // navigation/AppNavigator.tsx import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { HomeScreen } from '../screens/HomeScreen'; import { ProfileScreen } from '../screens/ProfileScreen'; import { OrderScreen } from '../screens/OrderScreen'; import { LoginScreen } from '../screens/LoginScreen'; export type RootStackParamList = { Main: undefined; Login: undefined; OrderDetail: { orderId: string }; }; export type MainTabParamList = { Home: undefined; Orders: undefined; Profile: undefined; }; const Stack = createNativeStackNavigator<RootStackParamList>(); const Tab = createBottomTabNavigator<MainTabParamList>(); const MainTabs = () => { return ( <Tab.Navigator screenOptions={{ tabBarActiveTintColor: '#007AFF', tabBarInactiveTintColor: '#8E8E93', tabBarStyle: { backgroundColor: '#FFFFFF', borderTopColor: '#E5E5E5', }, }} > <Tab.Screen name="Home" component={HomeScreen} options={{ title: '首页', tabBarIcon: ({ color }) => <HomeIcon color={color} />, }} /> <Tab.Screen name="Orders" component={OrderScreen} options={{ title: '订单', tabBarIcon: ({ color }) => <OrderIcon color={color} />, }} /> <Tab.Screen name="Profile" component={ProfileScreen} options={{ title: '我的', tabBarIcon: ({ color }) => <ProfileIcon color={color} />, }} /> </Tab.Navigator> ); }; export const AppNavigator = () => { const { isAuthenticated } = useAuth(); return ( <GestureHandlerRootView style={{ flex: 1 }}> <NavigationContainer> <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: '#007AFF', }, headerTintColor: '#FFFFFF', headerTitleStyle: { fontWeight: 'bold', }, }} > {!isAuthenticated ? ( <Stack.Screen name="Login" component={LoginScreen} options={{ headerShown: false }} /> ) : ( <> <Stack.Screen name="Main" component={MainTabs} options={{ headerShown: false }} /> <Stack.Screen name="OrderDetail" component={OrderDetailScreen} options={{ title: '订单详情' }} /> </> )} </Stack.Navigator> </NavigationContainer> </GestureHandlerRootView> ); };2.2 原生模块开发
// iOS原生模块 // LocalPods/BatteryModule/BatteryModule.swift import UIKit import React @objc(BatteryModule) class BatteryModule: NSObject { @objc static func requiresMainQueueSetup() -> Bool { return true } @objc func getBatteryLevel(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { DispatchQueue.main.async { UIDevice.current.isBatteryMonitoringEnabled = true let batteryLevel = UIDevice.current.batteryLevel if batteryLevel < 0 { resolve(["status": "unknown", "level": -1]) } else { let status = self.getBatteryStatus() resolve(["status": status, "level": batteryLevel * 100]) } } } private func getBatteryStatus() -> String { UIDevice.current.isBatteryMonitoringEnabled = true switch UIDevice.current.batteryState { case .charging: return "charging" case .full: return "full" case .unplugged: return "unplugged" case .unknown: return "unknown" @unknown default: return "unknown" } } } // 导出模块到React Native // LocalPods/BatteryModule/BatteryModuleBridge.m #import <React/RCTBridgeModule.h> @interface RCT_EXTERN_MODULE(BatteryModule, NSObject) RCT_EXTERN_METHOD(getBatteryLevel:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) @end2.3 性能优化
// 性能优化示例 import React, { useCallback, useMemo, memo } from 'react'; import { FlatList, StyleSheet, View, Text, TouchableOpacity, RefreshControl, } from 'react-native'; // 使用React.memo优化列表项 const OrderItem = memo<OrderItemProps>(({ order, onPress }) => { const { id, totalAmount, status, createdAt } = order; return ( <TouchableOpacity style={styles.orderItem} onPress={() => onPress(id)}> <View style={styles.orderHeader}> <Text style={styles.orderId}>订单号: {id}</Text> <StatusBadge status={status} /> </View> <View style={styles.orderFooter}> <Text style={styles.orderAmount}>¥{totalAmount.toFixed(2)}</Text> <Text style={styles.orderDate}>{formatDate(createdAt)}</Text> </View> </TouchableOpacity> ); }); OrderItem.displayName = 'OrderItem'; // 使用useCallback缓存回调 const OrderList: React.FC<OrderListProps> = ({ orders, onOrderPress }) => { const [refreshing, setRefreshing] = React.useState(false); // 使用useCallback避免不必要重新渲染 const handleRefresh = useCallback(() => { setRefreshing(true); fetchOrders().finally(() => setRefreshing(false)); }, []); const handleOrderPress = useCallback((orderId: string) => { onOrderPress(orderId); }, [onOrderPress]); // 使用keyExtractor优化列表渲染 const keyExtractor = useCallback((item: Order) => item.id, []); // 使用getItemLayout优化列表性能 const getItemLayout = useCallback( (_: any, index: number) => ({ length: 100, offset: 100 * index, index, }), [] ); const renderItem = useCallback( ({ item }: { item: Order }) => ( <OrderItem order={item} onPress={handleOrderPress} /> ), [handleOrderPress] ); return ( <FlatList data={orders} renderItem={renderItem} keyExtractor={keyExtractor} getItemLayout={getItemLayout} refreshControl={ <RefreshControl refreshing={refreshing} onRefresh={handleRefresh} /> } initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={true} ListEmptyComponent={<EmptyState />} contentContainerStyle={styles.listContent} /> ); }; // 图片优化 const OptimizedImage: React.FC<ImageProps> = memo(({ uri, style }) => { const [error, setError] = React.useState(false); return ( <Image source={{ uri }} style={[styles.image, style]} resizeMode="cover" loadingIndicatorSource={require('./placeholder.png')} progressiveRenderingEnabled={true} fadeDuration={300} onError={() => setError(true)} fallback={error} /> ); });三、Flutter开发
3.1 项目结构与配置
// 项目结构 /* lib/ ├── main.dart ├── app.dart ├── core/ # 核心模块 │ ├── constants/ │ │ ├── app_colors.dart │ │ ├── app_strings.dart │ │ └── app_dimensions.dart │ ├── theme/ │ │ ├── app_theme.dart │ │ └── text_styles.dart │ ├── utils/ │ │ ├── extensions.dart │ │ └── validators.dart │ └── errors/ │ └── failures.dart ├── data/ # 数据层 │ ├── models/ │ │ ├── user_model.dart │ │ └── order_model.dart │ ├── repositories/ │ │ ├── user_repository_impl.dart │ │ └── order_repository_impl.dart │ └── datasources/ │ ├── local/ │ └── remote/ ├── domain/ # 领域层 │ ├── entities/ │ │ ├── user.dart │ │ └── order.dart │ ├── repositories/ │ │ ├── user_repository.dart │ │ └── order_repository.dart │ └── usecases/ │ ├── get_user.dart │ └── create_order.dart ├── presentation/ # 表现层 │ ├── blocs/ # BLoC状态管理 │ │ ├── auth/ │ │ ├── order/ │ │ └── user/ │ ├── pages/ │ │ ├── home_page.dart │ │ ├── order_page.dart │ │ └── profile_page.dart │ └── widgets/ │ ├── common/ │ └── custom/ └── di/ # 依赖注入 └── injection.dart */ // pubspec.yaml // name: my_app // description: A cross-platform mobile application // publish_to: 'none' // version: 1.0.0+1 // environment: // sdk: '>=3.0.0 <4.0.0' // dependencies: // flutter: // sdk: flutter // cupertino_icons: ^1.0.2 // flutter_bloc: ^8.1.0 // get_it: ^7.6.0 // dio: ^5.0.0 // shared_preferences: ^2.2.0 // hive: ^2.2.3 // go_router: ^12.0.0 // json_annotation: ^4.8.0 // equatable: ^2.0.5 // freezed_annotation: ^2.4.0 // dev_dependencies: // flutter_test: // sdk: flutter // flutter_lints: ^3.0.0 // build_runner: ^2.4.0 // json_serializable: ^6.7.0 // freezed: ^2.4.0 // flutter: // uses-material-design: true // assets: // - assets/images/ // - assets/icons/3.2 状态管理(BLoC模式)
// 使用freezed定义不可变状态 // lib/domain/entities/order.dart import 'package:freezed_annotation/freezed_annotation.dart'; part 'order.freezed.dart'; part 'order.g.dart'; @freezed class Order with _$Order { const factory Order({ required String id, required String userId, required List<OrderItem> items, required double totalAmount, required OrderStatus status, required DateTime createdAt, }) = _Order; factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json); } @freezed class OrderItem with _$OrderItem { const factory OrderItem({ required String productId, required String productName, required int quantity, required double price, }) = _OrderItem; factory OrderItem.fromJson(Map<String, dynamic> json) => _$OrderItemFromJson(json); } enum OrderStatus { @JsonValue('pending') pending, @JsonValue('paid') paid, @JsonValue('shipped') shipped, @JsonValue('delivered') delivered, } // BLoC实现 // lib/presentation/blocs/order/order_event.dart part of 'order_bloc.dart'; abstract class OrderEvent extends Equatable { const OrderEvent(); @override List<Object?> get props => []; } class LoadOrders extends OrderEvent { const LoadOrders(); } class LoadMoreOrders extends OrderEvent { const LoadMoreOrders(); } class CreateOrder extends OrderEvent { final List<CreateOrderItem> items; const CreateOrder(this.items); @override List<Object?> get props => [items]; } class RefreshOrders extends OrderEvent { const RefreshOrders(); } // BLoC实现 // lib/presentation/blocs/order/order_state.dart part of 'order_bloc.dart'; @freezed class OrderState with _$OrderState { const factory OrderState({ @Default([]) List<Order> orders, @Default(AsyncValue<Unit>.loading()) AsyncValue<Unit> status, @Default(false) bool hasReachedMax, String? errorMessage, }) = _OrderState; } // lib/presentation/blocs/order/order_bloc.dart import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'order_bloc.freezed.dart'; class OrderBloc extends Bloc<OrderEvent, OrderState> { final OrderRepository orderRepository; OrderBloc({required this.orderRepository}) : super(const OrderState()) { on<LoadOrders>(_onLoadOrders); on<LoadMoreOrders>(_onLoadMoreOrders); on<CreateOrder>(_onCreateOrder); on<RefreshOrders>(_onRefreshOrders); } Future<void> _onLoadOrders( LoadOrders event, Emitter<OrderState> emit, ) async { emit(state.copyWith(status: const AsyncValue.loading())); try { final orders = await orderRepository.getOrders(page: 1); emit(state.copyWith( orders: orders, status: const AsyncValue.data(Unit), hasReachedMax: orders.isEmpty, )); } catch (e, st) { emit(state.copyWith( status: AsyncValue.error(e, st), errorMessage: e.toString(), )); } } Future<void> _onLoadMoreOrders( LoadMoreOrders event, Emitter<OrderState> emit, ) async { if (state.hasReachedMax) return; try { final nextPage = (state.orders.length ~/ 10) + 1; final newOrders = await orderRepository.getOrders(page: nextPage); emit(state.copyWith( orders: [...state.orders, ...newOrders], hasReachedMax: newOrders.isEmpty, )); } catch (e) { emit(state.copyWith(errorMessage: e.toString())); } } Future<void> _onCreateOrder( CreateOrder event, Emitter<OrderState> emit, ) async { emit(state.copyWith(status: const AsyncValue.loading())); try { final order = await orderRepository.createOrder(event.items); emit(state.copyWith( orders: [order, ...state.orders], status: const AsyncValue.data(Unit), )); } catch (e, st) { emit(state.copyWith( status: AsyncValue.error(e, st), errorMessage: e.toString(), )); } } }3.3 平台通道
// Dart端调用原生代码 // lib/services/battery_service.dart import 'package:flutter/services.dart'; class BatteryService { static const MethodChannel _channel = MethodChannel('com.example.app/battery'); Future<BatteryInfo> getBatteryLevel() async { try { final result = await _channel.invokeMethod<Map>('getBatteryLevel'); return BatteryInfo.fromMap(result!); } on PlatformException catch (e) { throw Exception('Failed to get battery level: ${e.message}'); } } Stream<BatteryLevelChange> get batteryLevelChanges { return _channel .receiveBroadcastStream() .map((event) => BatteryLevelChange.fromMap(event)); } } class BatteryInfo { final String status; final double level; BatteryInfo({required this.status, required this.level}); factory BatteryInfo.fromMap(Map<dynamic, dynamic> map) { return BatteryInfo( status: map['status'] as String, level: (map['level'] as num).toDouble(), ); } } class BatteryLevelChange { final double level; final DateTime timestamp; BatteryLevelChange({required this.level, required this.timestamp}); factory BatteryLevelChange.fromMap(Map<dynamic, dynamic> map) { return BatteryLevelChange( level: (map['level'] as num).toDouble(), timestamp: DateTime.now(), ); } } // iOS原生实现 // ios/Runner/AppDelegate.swift import Flutter import UIKit @main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller = window?.rootViewController as! FlutterViewController let batteryChannel = FlutterMethodChannel( name: "com.example.app/battery", binaryMessenger: controller.binaryMessenger ) batteryChannel.setMethodCallHandler { [weak self] (call, result) in if call.method == "getBatteryLevel" { self?.receiveBatteryLevel(result: result) } else { result(FlutterMethodNotImplemented) } } GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } private func receiveBatteryLevel(result: FlutterResult) { let device = UIDevice.current device.isBatteryMonitoringEnabled = true if device.batteryState == .unknown { result(FlutterError( code: "UNAVAILABLE", message: "Battery level not available", details: nil )) } else { let batteryLevel = device.batteryLevel result([ "status": self.batteryStatus(device.batteryState), "level": batteryLevel * 100 ]) } } private func batteryStatus(_ state: UIDevice.BatteryState) -> String { switch state { case .charging: return "charging" case .full: return "full" case .unplugged: return "unplugged" case .unknown: return "unknown" @unknown default: return "unknown" } } }四、Electron桌面开发
4.1 项目配置
// electron-builder.json { "appId": "com.example.app", "productName": "MyApp", "copyright": "Copyright © 2024", "directories": { "output": "release/${version}", "buildResources": "build" }, "files": [ "dist/**/*", "package.json" ], "mac": { "category": "public.app-category.productivity", "hardenedRuntime": true, "gatekeeperAssess": false, "entitlements": "build/entitlements.mac.plist", "entitlementsInherit": "build/entitlements.mac.plist", "target": [ { "target": "dmg", "arch": ["x64", "arm64"] }, { "target": "zip", "arch": ["x64", "arm64"] } ] }, "windows": { "target": [ { "target": "nsis", "arch": ["x64"] } ], "requestedExecutionLevel": "asInvoker" }, "linux": { "target": ["AppImage", "deb"], "category": "Utility" }, "nsis": { "oneClick": false, "allowToChangeInstallationDirectory": true, "createDesktopShortcut": true, "createStartMenuShortcut": true, "shortcutName": "MyApp" } }4.2 主进程与渲染进程通信
// electron/main.ts import { app, BrowserWindow, ipcMain, Menu, Tray, nativeImage } from 'electron'; import * as path from 'path'; import * as fs from 'fs'; let mainWindow: BrowserWindow | null = null; let tray: Tray | null = null; const isDev = process.env.NODE_ENV === 'development'; function createWindow(): void { mainWindow = new BrowserWindow({ width: 1200, height: 800, minWidth: 800, minHeight: 600, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, 'preload.js'), }, show: false, }); // 加载页面 if (isDev) { mainWindow.loadURL('http://localhost:3000'); mainWindow.webContents.openDevTools(); } else { mainWindow.loadFile(path.join(__dirname, '../dist/index.html')); } // 显示窗口 mainWindow.once('ready-to-show', () => { mainWindow?.show(); }); // 创建系统菜单 createMenu(); // 创建系统托盘 createTray(); mainWindow.on('closed', () => { mainWindow = null; }); } // IPC处理器 function setupIpcHandlers(): void { // 文件操作 ipcMain.handle('file:read', async (_, filePath: string) => { try { const content = await fs.promises.readFile(filePath, 'utf-8'); return { success: true, data: content }; } catch (error) { return { success: false, error: (error as Error).message }; } }); ipcMain.handle('file:write', async (_, filePath: string, content: string) => { try { await fs.promises.writeFile(filePath, content, 'utf-8'); return { success: true }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 系统信息 ipcMain.handle('system:getInfo', () => { return { platform: process.platform, arch: process.arch, version: process.getSystemVersion(), electronVersion: process.versions.electron, }; }); // 窗口控制 ipcMain.on('window:minimize', () => { mainWindow?.minimize(); }); ipcMain.on('window:maximize', () => { if (mainWindow?.isMaximized()) { mainWindow.unmaximize(); } else { mainWindow?.maximize(); } }); ipcMain.on('window:close', () => { mainWindow?.close(); }); } // 创建菜单 function createMenu(): void { const template: Electron.MenuItemConstructorOptions[] = [ { label: '文件', submenu: [ { label: '新建', accelerator: 'CmdOrCtrl+N', click: () => mainWindow?.webContents.send('menu:new'), }, { label: '打开', accelerator: 'CmdOrCtrl+O', click: () => mainWindow?.webContents.send('menu:open'), }, { label: '保存', accelerator: 'CmdOrCtrl+S', click: () => mainWindow?.webContents.send('menu:save'), }, { type: 'separator' }, { role: 'quit' }, ], }, { label: '编辑', submenu: [ { role: 'undo' }, { role: 'redo' }, { type: 'separator' }, { role: 'cut' }, { role: 'copy' }, { role: 'paste' }, ], }, { label: '视图', submenu: [ { role: 'reload' }, { role: 'forceReload' }, { role: 'toggleDevTools' }, { type: 'separator' }, { role: 'resetZoom' }, { role: 'zoomIn' }, { role: 'zoomOut' }, { type: 'separator' }, { role: 'togglefullscreen' }, ], }, ]; const menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); } // 创建托盘 function createTray(): void { const iconPath = path.join(__dirname, '../build/icon.png'); const icon = nativeImage.createFromPath(iconPath); tray = new Tray(icon); const contextMenu = Menu.buildFromTemplate([ { label: '显示窗口', click: () => mainWindow?.show(), }, { label: '关于', click: () => mainWindow?.webContents.send('menu:about'), }, { type: 'separator' }, { label: '退出', click: () => app.quit(), }, ]); tray.setToolTip('MyApp'); tray.setContextMenu(contextMenu); tray.on('click', () => { mainWindow?.show(); }); } // App生命周期 app.whenReady().then(() => { setupIpcHandlers(); createWindow(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } });五、微信小程序开发
5.1 项目结构
// 项目结构 /* miniprogram/ ├── src/ │ ├── app.js # 应用入口 │ ├── app.json # 全局配置 │ ├── app.wxss # 全局样式 │ ├── pages/ # 页面 │ │ ├── index/ # 首页 │ │ │ ├── index.js │ │ │ ├── index.json │ │ │ ├── index.wxml │ │ │ └── index.wxss │ │ ├── user/ # 用户页 │ │ └── order/ # 订单页 │ ├── components/ # 组件 │ │ ├── button/ │ │ ├── list/ │ │ └── empty/ │ ├── services/ # 服务 │ │ ├── api.js │ │ ├── auth.js │ │ └── storage.js │ ├── utils/ # 工具 │ │ ├── request.js │ │ └── format.js │ └── constants/ # 常量 │ └── index.js ├── cloudfunctions/ # 云函数 │ ├── login/ │ └── getUserInfo/ ├── sitemap.json └── project.config.json */ // miniprogram/app.json { "pages": [ "pages/index/index", "pages/user/index", "pages/order/index", "pages/order/detail" ], "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#007AFF", "navigationBarTitleText": "我的应用", "navigationBarTextStyle": "white" }, "tabBar": { "color": "#8E8E93", "selectedColor": "#007AFF", "backgroundColor": "#FFFFFF", "borderStyle": "black", "list": [ { "pagePath": "pages/index/index", "text": "首页", "iconPath": "assets/tabbar/home.png", "selectedIconPath": "assets/tabbar/home-active.png" }, { "pagePath": "pages/order/index", "text": "订单", "iconPath": "assets/tabbar/order.png", "selectedIconPath": "assets/tabbar/order-active.png" }, { "pagePath": "pages/user/index", "text": "我的", "iconPath": "assets/tabbar/user.png", "selectedIconPath": "assets/tabbar/user-active.png" } ] }, "style": "v2", "sitemapLocation": "sitemap.json" }5.2 微信API调用封装
// utils/request.js const BASE_URL = 'https://api.example.com'; const TOKEN_KEY = 'token'; class Request { constructor() { this.baseURL = BASE_URL; this.queue = new Map(); } request(options) { const { url, method = 'GET', data, header = {} } = options; const fullURL = `${this.baseURL}${url}`; return new Promise((resolve, reject) => { // 添加token const token = wx.getStorageSync(TOKEN_KEY); if (token) { header['Authorization'] = `Bearer ${token}`; } // 显示加载提示 if (options.showLoading !== false) { wx.showLoading({ title: '加载中...', mask: true }); } wx.request({ url: fullURL, method, data, header: { 'Content-Type': 'application/json', ...header, }, success: (res) => { wx.hideLoading(); if (res.statusCode === 200) { if (res.data.code === 0) { resolve(res.data.data); } else if (res.data.code === 401) { // token过期,重新登录 this.handleAuthError(); reject(new Error('Unauthorized')); } else { wx.showToast({ title: res.data.message || '请求失败', icon: 'none', }); reject(new Error(res.data.message)); } } else { this.handleHttpError(res.statusCode); reject(new Error(`HTTP Error: ${res.statusCode}`)); } }, fail: (err) => { wx.hideLoading(); wx.showToast({ title: '网络请求失败', icon: 'none', }); reject(err); }, }); }); } get(url, params, options = {}) { let queryString = ''; if (params) { queryString = '?' + Object.entries(params) .map(([key, value]) => `${key}=${value}`) .join('&'); } return this.request({ url: url + queryString, method: 'GET', ...options, }); } post(url, data, options = {}) { return this.request({ url, method: 'POST', data, ...options, }); } put(url, data, options = {}) { return this.request({ url, method: 'PUT', data, ...options, }); } delete(url, params, options = {}) { let queryString = ''; if (params) { queryString = '?' + Object.entries(params) .map(([key, value]) => `${key}=${value}`) .join('&'); } return this.request({ url: url + queryString, method: 'DELETE', ...options, }); } handleAuthError() { wx.removeStorageSync(TOKEN_KEY); wx.navigateTo({ url: '/pages/login/index' }); } handleHttpError(statusCode) { const errorMessages = { 400: '请求参数错误', 403: '拒绝访问', 404: '资源不存在', 500: '服务器错误', 502: '网关错误', 503: '服务不可用', 504: '网关超时', }; wx.showToast({ title: errorMessages[statusCode] || '请求失败', icon: 'none', }); } } export const request = new Request(); // services/api.js export const userAPI = { login: (code) => request.post('/api/login', { code }), getUserInfo: () => request.get('/api/user/info'), updateUserInfo: (data) => request.put('/api/user/info', data), }; export const orderAPI = { getOrders: (params) => request.get('/api/orders', params), getOrderDetail: (id) => request.get(`/api/orders/${id}`), createOrder: (data) => request.post('/api/orders', data), cancelOrder: (id) => request.delete(`/api/orders/${id}`), };总结
跨平台开发是现代应用开发的重要方向。本文系统性地介绍了各种跨平台开发方案:
- React Native:适合团队有Web背景,使用JavaScript/TypeScript
- Flutter:适合对性能要求高,追求原生体验
- Electron:适合桌面应用开发,Web技术栈
- 微信小程序:适合国内小程序生态
选择跨平台框架的关键考虑因素:
- 团队技术栈和经验
- 性能要求
- 目标平台
- 开发周期和成本
- 生态系统支持
跨平台开发的核心要点:
- 业务逻辑尽量共享,平台特定代码最小化
- 建立统一的架构和代码规范
- 注重性能优化和用户体验
- 做好测试,确保跨平台一致性
希望本文能够帮助大家选择合适的跨平台方案,并在实际项目中顺利实现跨平台开发。