跨平台UI开发Flutter和ReactNative怎么选:一套代码做iOS和安卓App的实战经验
去年我们团队接了一个电商类App项目,需求很明确:同一套代码,iOS和Android同时上线,后续维护不能养两套原生团队。当时Flutter 3.7和React Native 0.72都在主流版本里,我们内部吵了一周,最后两边都写了Demo跑了一遍,才真正拍板。今天把这些年的实战经验、踩坑细节和选择逻辑摊开来讲,不站队,只讲真实体感。
先搞清楚一件事:它们“画”界面的方式完全不同
很多人把Flutter和RN都叫“跨平台框架”,但它们的底层逻辑其实像两种完全不同的造车思路。
React Native 更像是在本地请了熟悉规则的工人。你的业务代码是JavaScript/TypeScript,它通过桥接(Bridge)或者新的 JSI 通道,把指令翻译成 iOS 的 UIKit 组件或 Android 的 View 组件。也就是说,按钮在 iPhone 上就是苹果原生的按钮,在安卓上就是 Material Design 风格的按钮。好处是“长得像系统应用”,坏处是同一个组件在不同平台可能渲染有细微差异,尤其是复杂动画或自定义布局时。
Flutter 则像是自带了一套完整的画笔和颜料。它不依赖平台的UI控件,而是用 Skia(现在逐步切换到 Impeller)引擎直接在屏幕上绘制像素。你在代码里写的 Container、Row、Text,最终都会被 Flutter 自己画出来。所以无论在 iPhone 还是安卓,界面几乎一模一样。好处是像素级可控,坏处是每次都要多带一个渲染引擎,安装包会大一些。
如果用小朋友能听懂的话说:RN 是去超市买现成的乐高积木拼房子,Flutter 是自己揉泥巴捏房子。买积木快、符合当地习惯;捏泥巴慢一点,但你想捏成什么样就什么样。
同一段“点击计数”代码,长什么样?
看代码是最直观的对比。下面是一个最简单的计数器,两个框架各写一份,你能立刻感受到语法和思维方式的差异。
Flutter(Dart)
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('计数器')),
body: Center(child: CounterPage()),
),
);
}
}
class CounterPage extends StatefulWidget {
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('点了 $_count 次', style: const TextStyle(fontSize: 24)),
const SizedBox(height: 20),
FloatingActionButton(onPressed: _increment, child: const Icon(Icons.add)),
],
);
}
}
React Native(TypeScript + Hooks)
import React, { useState } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
export default function App() {
const [count, setCount] = useState(0);
return (
<View style={styles.container}>
<Text style={styles.title}>计数器</Text>
<Text style={styles.count}>点了 {count} 次</Text>
<TouchableOpacity style={styles.button} onPress={() => setCount(count + 1)}>
<Text style={styles.buttonText}>+</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
title: { fontSize: 28, fontWeight: 'bold', marginBottom: 16 },
count: { fontSize: 22, marginBottom: 32 },
button: { backgroundColor: '#1e88e5', paddingVertical: 14, paddingHorizontal: 32, borderRadius: 8 },
buttonText: { color: '#fff', fontSize: 20, fontWeight: '600' },
});
看懂了吗?Flutter 的代码更“声明式+树形结构”,所有东西都是 Widget,嵌套关系一目了然;RN 更接近 Web 开发,StyleSheet、TouchableOpacity、useState 这些概念前端同学上手极快。如果你的团队之前做过 React 或 Vue,RN 的学习曲线几乎是贴着地面的;如果团队愿意学 Dart,Flutter 的语法其实非常干净,没有回调地狱,异步用 async/await 也很自然。
性能到底谁更快?别被营销话术骗了
早期 RN 因为 JS 桥接延迟,被吐槽“卡顿”;早期 Flutter 因为引擎占用内存高,被吐槽“臃肿”。现在两者都已经大幅进化,但体感差异依然存在。
| 维度 | Flutter | React Native |
|---|---|---|
| 渲染路径 | 自绘引擎(Skia/Impeller) | 原生组件 + JS 线程调度 |
| 动画流畅度 | 60/120fps 稳定,复杂动效轻松 | 简单交互够用,复杂手势需 Reanimated |
| 首屏启动 | 略慢(需加载引擎) | 略快(原生壳子直接启动) |
| 安装包体积 | iOS 约 20-30MB,Android 约 15-25MB | iOS 约 5-10MB,Android 约 3-8MB(Hermes 开启后) |
| 热更新 | 不支持官方热更新(受 Apple 政策限制) | 支持 CodePush / Expo Updates |
| 新架构状态 | Impeller 默认,性能稳定 | Fabric + TurboModules 已成熟,JSI 取代 Bridge |
实战中我们测过一组数据:一个包含 50 个商品卡片的列表页,Flutter 滑动帧率稳定在 58-60fps;RN 在低端安卓机上偶尔掉到 45-50fps,但在 iPhone 上基本持平。如果你做的是工具类、内容浏览类、电商类,两者都能满足;如果是重度动画、视频剪辑、游戏化交互,Flutter 的自绘优势会更明显。
“一套代码”是个美丽的误会:真实比例是多少?
很多人以为跨平台就是“写完一次,到处运行”。现实是:70%-85% 的业务逻辑和 UI 可以共享,剩下 15%-30% 必须和平台打交道。
比如这些场景,你躲不开:
- iOS 的导航栏样式、安卓的状态栏适配
- 微信支付走微信 SDK,支付宝走支付宝 SDK
- 推送:iOS 用 APNs,Android 用厂商推送(华为、小米、OPPO 各自不同)
- 权限弹窗:iOS 是系统统一弹窗,Android 是应用内自定义
- 应用内购买:iOS 走 StoreKit,Android 走 Google Play Billing
我们当时的做法是建一个 platform 目录,用条件导入隔离平台差异:
Flutter 示例
// lib/services/push_service.dart
import 'package:flutter/foundation.dart';
abstract class PushService {
Future<void> init();
Future<String?> getToken();
}
// lib/platform/android_push_service.dart
import '../services/push_service.dart';
class AndroidPushService implements PushService {
@override Future<void> init() async { /* 接入 Firebase + 厂商推送 */ }
@override Future<String?> getToken() async { /* 返回 FCM token */ }
}
// lib/platform/ios_push_service.dart
import '../services/push_service.dart';
class IOSPushService implements PushService {
@override Future<void> init() async { /* 接入 APNs */ }
@override Future<String?> getToken() async { /* 返回 device token */ }
}
// 统一入口
PushService get pushService {
if (kIsWeb) throw UnsupportedError('暂不支持Web');
if (Platform.isIOS) return IOSPushService();
if (Platform.isAndroid) return AndroidPushService();
throw UnsupportedError('Unsupported platform');
}
RN 示例
// src/services/pushService.ts
import { Platform } from 'react-native';
export const getPushService = () => {
if (Platform.OS === 'ios') return import('../platform/iosPush');
if (Platform.OS === 'android') return import('../platform/androidPush');
throw new Error('Unsupported platform');
};
这套模式的好处是:业务层永远只调用 pushService.init(),不用关心底层是 iOS 还是 Android。等以后要做小程序或 Web,再加一个实现类就行。
生态和第三方库:谁更好找?
这一点必须说实话:React Native 的 npm 生态目前仍然碾压。
你遇到一个需求,比如“扫码登录网页”、“人脸识别”、“蓝牙打印”,在 npm 里搜一下,大概率已经有封装好的包。Flutter 的 pub.dev 也在快速增长,但有些冷门场景你得自己写 Platform Channel 或原生插件。这不是能力问题,是社区规模问题。
不过 Flutter 的插件质量普遍更高。因为 Dart 类型系统严格,很多包作者会认真维护;RN 的 npm 包鱼龙混杂,npm install 之前最好看看:最后更新时间、Stars、Issues 回复速度、是否支持新架构。我们踩过一个坑:一个看起来活跃的 RN 定位库,实际上还在用旧版 Bridge API,升级到 0.73 后直接崩。
建议的选型检查清单:
- 核心依赖在目标生态里有没有成熟方案?
- 团队是否愿意为缺少的库写原生桥接?
- 第三方 SDK 是否提供官方跨平台封装?(比如微信、支付宝、地图)
调试和工程化:日常开发的真实体验
这部分外人很少提,但对开发效率影响极大。
Flutter 的优势:
flutter run一行命令同时启动 iOS 模拟器 + Android 模拟器- DevTools 内置性能好,Widget Inspector 可以直接点选查看层级和样式
- Hot Reload 几乎秒级,改 UI 不用重新编译
- 类型安全从第一天就生效,Dart 的 null safety 减少了大量运行时崩溃
RN 的优势:
- React DevTools、Flipper、Metro Bundler 生态成熟
- 前端工具链无缝迁移:ESLint、Prettier、Jest、Storybook 都能用
- 调试 JS 逻辑和调试 Web 几乎一样,心理负担小
- 支持 Hermes 引擎后,内存占用和启动速度明显改善
我们团队实际开发时,Flutter 项目基本不需要额外配调试环境,开箱即用;RN 项目则需要认真配置 Metro、Hermes、Flipper,尤其是新架构开启后,部分旧插件会报错,需要逐个排查。
那到底怎么选?按场景对号入座
我不喜欢给绝对答案,因为“最好”永远取决于你的约束条件。下面这几个判断标准,是我们团队反复验证过的:
选 React Native,如果:
- 团队里有成熟的 Web 前端工程师,想快速转移动端
- 产品需要频繁发版,且希望支持 JS 热更新绕过审核
- 界面不需要像素级跨平台一致,允许跟随系统风格
- 已有大量 React 组件库或 Web 项目,想复用逻辑
- 预算紧、周期短,优先跑通 MVP
选 Flutter,如果:
- 设计稿非常精细,要求 iOS 和 Android 完全一致
- 团队愿意学习 Dart,追求长期稳定的渲染表现
- 产品包含大量自定义动画、复杂表单、地图、图表
- 不想依赖平台原生组件的微小差异
- 计划未来扩展到 Web、Desktop(macOS/Windows/Linux)
两边都不完美,但可以混合: 有些公司用 RN 做主框架,核心性能模块用原生写;有些用 Flutter 做 UI,关键支付/推送模块保留原生。这完全没问题,关键是架构边界要清晰。
给想入坑的朋友几个实在建议
- 别一上来就追求“完美跨平台”。先跑通一个完整流程:登录、列表、详情、提交、跳转。跨平台的痛苦往往不在 Hello World,而在真实业务链路。
- 把平台差异当成第一优先级来设计。在写第一行业务代码前,先列出哪些功能必须走原生,再决定框架。
- 固定版本,别追新。Flutter 3.19 和 3.22 差很多,RN 0.72 和 0.75 也差很多。生产环境尽量用 LTS 或稳定版本,新特性等社区验证半年再用。
- CI/CD 提前规划。GitHub Actions 可以同时构建 iOS 和 Android,但签名、证书、TestFlight、Google Play Console 的对接各有坑。别等到上线前一周才搞。
- 如果只能选一个方向,先做一个真实项目。500 行代码的 Demo 骗不了你,10000 行业务的痛才会告诉你答案。
说到底,Flutter 和 React Native 都不是银弹,也没有谁一定能“淘汰”谁。RN 赢在生态和前端迁移成本,Flutter 赢在渲染一致性和工程整洁度。你选哪个,本质上是在选你的团队基因、产品形态和长期维护策略。
如果你现在正站在十字路口,我的建议是:拿你们现有的代码库、设计师的稿子、产品经理的排期,分别用两个框架各写一个 3 天的原型。跑完你会发现,答案早就藏在团队的日常习惯里了。