基础与工程篇-我为什么用 Flutter 做中大型业务

4 阅读6分钟

我为什么用 Flutter 做中大型业务(选型与边界)

这是我 Flutter 实战系列的第一篇。
这篇不讲“语法入门”,只讲一件事:为什么我最终从 uniappx 迁移到 Flutter,并把它作为中大型业务主技术栈


1. 问题背景:业务场景 + 现象

我负责的是一个偏“重交互”的业务项目,核心特征如下:

  • 页面多、模块多(房间、聊天、支付、活动、广告、用户体系等)
  • UI 复杂(大量自定义组件、动态布局、动画与状态叠加)
  • 实时性要求高(音视频、WebSocket、多状态同步)
  • 要求 iOS / Android 双端持续快速迭代

项目早期技术路线是跨平台方案,经历过对 RN、uniapp、uniappx 的调研和实践。最终我选择了 从 uniappx 迁移到 Flutter 重构

迁移前我们遇到的“现象级问题”非常典型:

  1. 打包与构建流程重、慢、复杂,研发与测试反馈回路拉长
  2. 生态不成熟,关键能力缺插件或插件质量不稳定
  3. 复杂功能需要频繁手写原生桥接,跨端效率优势被抵消
  4. 复杂 UI 还原成本高,尤其在细节一致性和性能稳定性上
  5. 性能瓶颈明显,高频刷新场景更容易抖动/卡顿

2. 原因分析:核心原理 + 排查过程

我把问题拆成“工程效率、生态、性能、UI 可控性、长期维护成本”五个维度分析。

2.1 为什么放弃 RN(React Native)

核心原理层面:

  • RN 采用 JS 逻辑 + 原生渲染的桥接模型,复杂场景下桥接通信成本不低
  • 对“高频状态更新 + 动画 + 实时互动”类页面,性能调优窗口较窄
  • 关键问题一旦落到原生侧,需要团队具备双端原生深入能力

排查过程中的实际感受:

  • 功能实现速度并不总是快,很多时候在“桥接 + 生命周期 + 差异行为”上花掉时间
  • 团队成员能力结构不一致时,排障成本会被放大

结论:RN 适合很多业务,但对我们这类“重实时 + 重复杂交互”的中大型项目,长期维护压力偏高。


2.2 为什么放弃 uniapp / uniappx

先说结论:uniappx 有进步,但在我们项目类型上仍不够“工程化可控”。

放弃 uniapp 的主要原因
  • 复杂 UI 还原和性能稳定性在高要求场景下不理想
  • 组件/插件能力与项目深度需求之间存在落差
放弃 uniappx 的主要原因(本项目真实痛点)
  1. 打包、构建繁重
    • 构建链条复杂,调试反馈周期偏长
  2. 生态差距明显
    • 业务常用能力的成熟插件不足,或兼容性/可维护性一般
  3. 大量插件需要手撸原生
    • 本应“跨平台省成本”,但实际变成“跨平台 + 原生双倍维护”
  4. 性能瓶颈
    • 在多模块叠加、复杂渲染与高频刷新场景下,优化上限受限
  5. 复杂 UI 还原难度高
    • 高精度视觉稿和动态特效落地成本高,且双端一致性难控

结论:uniappx 对轻中型业务有其价值,但对于我们这种中大型、复杂交互项目,风险和隐性成本偏高。


3. 解决方案:方案对比 + 最终选择

3.1 备选方案对比(简版)

维度RNuniapp / uniappxFlutter
复杂 UI 还原中-偏低
实时互动性能中-偏低
生态与稳定插件中-高
原生依赖成本中-高中-高
团队长期维护中-偏低

3.2 最终选择:Flutter

我最终选 Flutter,不是因为“它万能”,而是它在我们的业务约束下更均衡:

  • 渲染层可控,复杂 UI 还原能力强
  • 动画与交互表达力好,统一性强
  • 生态成熟,主流业务能力可快速落地
  • 架构可塑性好,适合中大型分层治理
  • 与原生互通路径清晰,关键能力可兜底

3.3 同时明确边界(很重要)

Flutter 也不是银弹,下面这些边界要提前接受:

  • 超深原生能力(特别是系统级、厂商定制能力)仍需原生开发
  • 包体、启动、内存等仍要做工程化治理
  • 团队要统一代码规范与架构,不然项目后期照样会乱

4. 关键代码:最小必要代码片段

这篇偏“选型”,代码只给最小骨架,展示我在 Flutter 项目里如何做“中大型可维护分层”。

4.1 模块化目录(示意)

lib/
  modules/
    room/
      pages/
      widgets/
      view-model/
      state/
      server/
    wallet/
      pages/
      widgets/
      view-model/
      server/
  common/
    api/
    http/
    router/
  startup/

4.2 网络层与业务层分离(示意)

class OrderServer {
  static Future<Map<String, dynamic>?> applePayOrder(Map<String, dynamic> body) async {
    final response = await request('/pay/apple/order', params: body, isLoading: false);
    return response;
  }
}
class WalletViewModel extends StateNotifier<WalletState> {
  WalletViewModel() : super(WalletState.initial());

  Future<void> createAppleOrder(String productId) async {
    final data = await OrderServer.applePayOrder({
      'productId': productId,
      'businessType': 'diamond',
    });
    // 业务状态只在 VM 内收敛
    state = state.copyWith(lastOrderNo: data?['orderNo']);
  }
}

4.3 页面只做“状态消费与事件分发”(示意)

class WalletPaidProductPage extends ConsumerWidget {
  const WalletPaidProductPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(walletProvider);
    final vm = ref.read(walletProvider.notifier);

    return ElevatedButton(
      onPressed: () => vm.createAppleOrder(state.selectedProductId),
      child: const Text('确认充值'),
    );
  }
}

这套分层的目的:把“页面复杂度”压下来,把“业务复杂度”放进可测试、可演进的 ViewModel/Server 层。


5. 效果验证:数据 / 截图 / 日志

以下是我迁移后重点关注的验证项

5.1 工程效率

  • 本地调试反馈速度是否提升
  • 构建成功率是否更稳定
  • CI 构建失败率是否下降

5.2 体验与性能

  • 列表滑动帧率是否稳定
  • 复杂页面首帧时间是否可接受
  • 高并发状态更新时是否出现明显抖动

5.3 交付质量

  • 同类线上问题重复率是否下降
  • 原生桥接故障占比是否下降
  • 新需求从评审到上线的周期是否缩短

5.4 建议附上的证据

  • flutter run --profile 关键日志
  • DevTools 截图(CPU / Memory / Frame)
  • 发版前后 crash 统计对比图
  • 构建时长对比表

6. 可复用结论:通用经验 + 避坑清单

6.1 通用经验(可直接复用)

  1. 选型先看业务形态,不先看“技术热度”
  2. 跨平台效率不等于全生命周期效率(开发快 ≠ 维护快)
  3. 中大型项目优先考虑“可治理性”(分层、规范、测试、CI)
  4. 复杂交互项目,渲染与性能上限是关键指标
  5. 要有原生兜底能力,但不要把 60% 工作都逼回原生

6.2 避坑清单(血泪版)

  • 不要在选型初期只做 TodoDemo,对真实业务压测太少
  • 不要忽略插件维护活跃度和社区成熟度
  • 不要让页面直接承载业务逻辑(后期必崩)
  • 不要晚做性能基线,越晚成本越高
  • 不要把“迁移”当成纯技术任务,必须同步业务节奏和测试资源

结语

我选择 Flutter 的核心原因不是“它最先进”,而是它在我们项目里做到了:

  • 复杂 UI 可控
  • 性能可优化
  • 工程可治理
  • 长期可维护