React Native 和 Flutter 写同一套业务,我们在维护成本上的差距是怎么拉开的
我们同时维护着一套 React Native 和一套 Flutter 应用,跑的是完全相同的电商核心业务流程。两年下来,React Native 端的维护工时是 Flutter 端的 2.3 倍。这个数字是 Jira 上统计出来的实际工单耗时,不是主观感受。
差距不是一开始就有的。两个项目都是从 2022 年 Q2 启动,初始功能交付速度差不多。真正的分水岭出现在第一个大版本迭代之后——当我们开始频繁改业务逻辑、升级依赖、修线上问题的时候,Flutter 那边的改动通常局限在几个文件,而 React Native 这边经常牵一发而动全身。
下面按维度拆解这个差距是怎么拉开的。
依赖管理的熵增速度完全不同
React Native 项目的依赖地狱是维护成本的第一大吞噬者。我们统计过 2024 年 3 月那次大版本升级的工时:把 react-native 从 0.70 升到 0.73,连带升级 react-navigation 6.x 到 7.x、react-native-reanimated 升级、Metro 配置迁移,总共花了 4 个工程师 3 个完整工作日——合计约 96 人时。
问题出在 Native Module 的碎片化。一个典型的 React Native 电商应用会依赖 15-20 个需要原生桥接的第三方库:相机、扫码、推送、地图、支付、图片缓存、WebView、动画库等等。这些库各自的版本兼容矩阵都不一样。举个例子,react-native-camera 停更后社区分裂成 react-native-vision-camera 和 expo-camera,前者要求 react-native 0.71+ 且 Kotlin 1.8+,后者绑定了 Expo SDK 的版本号。你升级任何一个都可能在编译时炸掉。
Flutter 这边呢?同样的功能集,我们用的是官方维护的 camera 插件、google_maps_flutter、firebase_messaging 等。这些插件由 Flutter 团队或 Google 官方维护,版本号跟 Flutter SDK 强绑定。2024 年 5 月我们从 Flutter 3.19 升到 3.22,pubspec.yaml 里只改了 sdk 版本号,跑了 flutter pub upgrade,22 个依赖自动对齐,编译一次通过。工时:1 人时。
这不是运气好。Flutter 的插件体系建立在统一的 Platform Channel 抽象上,原生端的代码量极少且由官方模板生成,兼容性破坏集中在 Dart 层,而 Dart 的类型系统在编译期就能捕获绝大多数问题。React Native 的原生桥接代码是手写的,每个维护者水平参差不齐,版本兼容策略各自为政。
状态管理的重构代价差一个数量级
我们两端的业务逻辑都用了相似的架构:全局状态 + 局部状态,网络层统一封装,缓存策略一致。React Native 这边用的 Redux Toolkit + RTK Query,Flutter 这边用的 Riverpod + Dio。
2024 年 Q1 有个典型的需求:把订单详情页的支付流程从"先选支付方式再确认"改成"一键下单后自动选默认支付方式"。这涉及到订单状态机、支付状态、用户配置三个数据流的交汇。
React Native 这边改动了 11 个文件:Redux slice 3 个(order、payment、userConfig),中间件逻辑调整,4 个组件里的 dispatch 调用改参数,2 个 screen 的 navigation params 类型定义更新,还有 1 个 selector 的性能优化(因为加了新的派生状态导致不必要的 re-render)。Code Review 时发现一个 bug:某个深层嵌套组件通过 useSelector 取了整个 order 对象,新增字段触发了意料外的渲染循环,又加了 Reselect 的 memoization 才解决。合计工时:约 14 人时。
Flutter 这边改动 4 个文件:OrderNotifier 里加了一个方法,PaymentNotifier 里调整了默认支付方式的逻辑,两个 Widget 的 Consumer 改了一下读取的 provider 类型。Riverpod 的 provider 是编译期确定依赖关系的,新增的 provider 组合不会影响不相关的 Widget 重建。Dart 的静态分析直接提示了哪个 Widget 需要更新。合计工时:约 4 人时。
根本原因不是 Redux vs Riverpod 的优劣,而是 JS 的动态类型 + React 的运行时渲染模型,让状态变更的影响面分析极度依赖人工。你改了一个 action 的 payload 类型,TypeScript 能帮你找到所有调用的地方,但它没办法告诉你哪个 useSelector 会因为引用变化而触发重渲染。Flutter 这边,provider 的依赖图是显式声明的,Widget 的重建范围由框架精确控制,不需要开发者手动 memo。
平台特性适配的隐性成本
写业务逻辑的时候两边差不多,但一到跟原生打交道的地方,差距就出来了。
以"后台语音播报"这个功能为例——订单状态变更时,即使 App 在后台也要语音通知用户。React Native 的实现路径:找到 react-native-track-player 这个库,发现它最新的 4.x 版本只支持 react-native 0.72+,我们的 0.71 版本不兼容。退回 3.x 版本,但 3.x 的 iOS 后台模式配置文档过时了,实际要手动改 Info.plist 的 UIBackgroundModes 和 Capabilities 里的 Background Modes 勾选。Android 端更麻烦,需要分别在 AndroidManifest.xml 里声明 foreground service 权限,还要处理 Android 12+ 的 exact alarm 权限弹窗逻辑。两个平台的实现细节不同,最终写了 80 行的原生桥接代码(iOS 的 Swift + Android 的 Kotlin),加上 JS 层的封装,总共 3 天。
Flutter 这边:audio_service 插件官方维护,README 里的配置说明覆盖了 iOS 和 Android 的最新版本要求。照着文档配好 AudioSession 和 notification channel,Dart 层写一个 BackgroundAudioTask 类,150 行代码全部在 Dart 侧完成。1 天搞定。
这不是个例。我们在两个项目上统计过涉及原生功能的需求,React Native 端平均每个需求比 Flutter 端多花 2.1 倍的工时,多出来的时间全部消耗在:找兼容版本、读第三方库的 Native 源码排查问题、写平台特定的原生代码、处理 Android/iOS 版本差异。
UI 一致性的维护负担
电商业务有大量的列表、表单、弹窗、动画。React Native 这边的 UI bug 有一类特别高频:同一套 StyleSheet 在 iOS 和 Android 上渲染结果不一致。比如 Shadow 属性在 Android 上不生效要用 elevation;Text 组件的行高在两个平台上计算基准不同导致文字被截断;FlatList 在快速滚动时 Android 上白屏但 iOS 上正常。
我们维护了一份 200 行的平台适配工具函数,专门处理这些差异。每个新加入的开发者都需要先理解哪些样式属性是跨平台一致的、哪些不是。2024 年我们做过一次统计,React Native 端 37% 的 UI 相关 bug 是因为开发者不知道某个样式在某个平台上的行为不同而引入的。
Flutter 这边的 UI bug 集中在:Material Design 和 Cupertino 的视觉差异、自定义画布渲染的性能。但至少,同样的 Container、同样的 BoxShadow,在 iOS 和 Android 上渲染出来是像素级一致的。Skia 引擎保证了这一点。我们不需要维护一套平台差异知识库。
构建与发布流程的稳定性
CI/CD 管线的维护时间也是成本。React Native 的 iOS 构建在过去两年里出过 6 次"某次依赖升级后 Archive 失败"的问题,每次都跟 CocoaPods 的版本、Xcode 的版本、某个 Native Module 的 podspec 配置有关。最近一次是 2024 年 6 月,Xcode 15.3 默认启用了新的链接器,导致 react-native-firebase 的一个符号重复,临时加了 EXCLUDED_ARCHS 的 workaround 才通过。这种问题 Flutter 这边没遇到过——Flutter 的 iOS 构建不依赖 CocoaPods 的复杂依赖解析,插件通过 .podspec 自动生成,整个过程是 flutter 命令行工具统一管理的。
Android 侧也有类似的对比。React Native 的 Android 构建依赖开发者的原生工程配置,gradle 版本、Kotlin 版本、compileSdk 版本都需要手动对齐。Flutter 的 Android 构建由 Flutter 工具链自动生成和管理,开发者几乎不需要碰 build.gradle。
常见问题
两个框架你们团队各有多少人?技术水平差异会不会影响结论?
React Native 端 3 人,Flutter 端也是 3 人,都是 3 年以上移动开发经验。实际上 React Native 端有两个开发者之前是做原生 iOS 的,对 Native 层更熟悉。如果换成纯前端背景的团队,React Native 的原生问题解决成本可能更高。
React Native 用了 Expo 会不会缩小差距?
我们评估过迁移到 Expo,但现有项目深度依赖了多个自定义 Native Module(对接了第三方 POS 机 SDK、定制的蓝牙打印协议),这些没办法通过 Expo 的插件体系覆盖。如果是纯 UI + 网络请求的业务,Expo 确实能大幅降低原生维护成本,但我们的场景不适用。另外 Expo 的 SDK 版本绑定策略跟 Flutter 很像,这恰好说明维护成本的核心差异在于原生层的管理方式。
你们统计的工时数据有考虑学习曲线吗?
没有。统计起点是两个项目都完成了初始交付、团队对各自框架已经熟练之后的阶段。初始学习阶段 React Native 因为 JS 生态的原因上手确实更快,但熟练之后日常维护的效率曲线,Flutter 明显更平缓。换句话说,React Native 的维护成本随着时间推移加速增长,Flutter 的维护成本基本保持线性。