4、状态管理选型:Provider vs Bloc vs GetX vs Riverpod,选型对比与实战

状态管理,说白了就是 Flutter 项目里的「数据大管家」。

我见过不少团队,一开始随便选了个方案,结果项目做到一半发现扩展不动了,重构成本高得吓人。所以这一章,咱们就把市面上最主流的四个方案掰开揉碎,看看它们到底适合什么场景。

4.1 为什么需要状态管理?

先问个问题:你写 Flutter 时,有没有遇到过「A 页面改了数据,B 页面死活不更新」的情况?

这就是典型的「状态共享」问题。Flutter 本身是声明式 UI,数据变了,UI 要跟着变。但如果没有一个统一的管理机制,你只能靠回调一层层传,代码很快就变成一团乱麻。

我个人习惯是:小项目用 setState 就够了,但一旦超过 3 个页面需要共享数据,就必须上状态管理框架。

4.2 Provider:官方推荐,入门首选

Provider 是 Google 官方推荐的方案,也是我最早接触的状态管理库。它的核心思想就一句话:把数据放到顶层,谁用谁拿

核心特点:

  • 基于 InheritedWidget 封装,学习成本低
  • 配合 ChangeNotifier 使用,写法直观
  • 社区生态成熟,文档齐全

来个最简单的例子:

class CounterProvider extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // 通知 UI 更新
  }
}

// 使用
final counter = context.watch<CounterProvider>();
Text('${counter.count}');

嗯,这里要注意:context.watch 会让整个 widget 重建。如果你只想读数据不重建,用 context.read

我的经验:Provider 适合中小型项目,特别是团队里新手比较多的时候。我在一个电商 App 里用过 Provider,页面层级不深,开发效率确实高。但一旦涉及到跨页面复杂状态同步,Provider 的嵌套问题就开始让人头疼了。

4.3 Bloc:企业级项目的「正规军」

Bloc 全称是 Business Logic Component,说白了就是把业务逻辑和 UI 彻底分离。

我第一次用 Bloc 时觉得它太啰嗦了——一个简单的计数器要写事件、状态、Bloc 三个文件。但后来在大型项目中,这种「啰嗦」反而成了优势。

核心特点:

  • 强制使用事件驱动,逻辑清晰可追溯
  • 支持响应式编程,测试友好
  • 适合多人协作的大型项目

看个计数器例子:

// 事件
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}

// 状态
class CounterState {
  final int count;
  CounterState(this.count);
}

// Bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0)) {
    on<IncrementEvent>((event, emit) {
      emit(CounterState(state.count + 1));
    });
  }
}

// UI 使用
BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) {
    return Text('${state.count}');
  },
);

避坑指南:我曾经在一个项目中过度使用 Bloc,每个小组件都搞一个 Bloc,结果文件数量爆炸。后来我总结了一个原则:只有跨页面共享的复杂逻辑才用 Bloc,页面内部的简单状态用 setState 就够了。

4.4 GetX:轻量级「瑞士军刀」

GetX 是个「全能型选手」,它不光管状态,还管路由、依赖注入、国际化……说白了,它想让你少写很多样板代码。

核心特点:

  • 零依赖,包体积小
  • 语法简洁,开发效率极高
  • 内置路由和依赖管理

GetX 的计数器写法,你感受一下:

class CounterController extends GetxController {
  var count = 0.obs; // 可观察变量

  void increment() => count++;
}

// UI 使用
final controller = Get.put(CounterController());
Obx(() => Text('${controller.count}'));

是不是很简洁?但简洁背后也有代价。

我的看法:GetX 适合快速原型开发,或者个人项目。但我在团队项目里用过一次后,发现它的「魔法」太多了——Get.putGet.find 这些全局方法让依赖关系变得隐晦,新人接手时很难理清数据流向。说白了,GetX 的灵活性是把双刃剑

4.5 Riverpod:Provider 的「进化版」

Riverpod 是同一个作者对 Provider 的反思和重构。它解决了 Provider 的几个痛点:编译时安全、不依赖 BuildContext、支持异步状态。

核心特点:

  • 不依赖 Widget 树,可以在任何地方访问
  • 编译时类型安全,减少运行时错误
  • 原生支持异步和缓存

Riverpod 的写法:

final counterProvider = StateProvider<int>((ref) => 0);

// UI 使用
final count = ref.watch(counterProvider);
Text('$count');

// 更新
ref.read(counterProvider.notifier).state++;

我的推荐:如果你正在新项目选型,我个人建议优先考虑 Riverpod。它吸收了 Provider 的简洁,又弥补了它的不足。我在一个中型项目中从 Provider 迁移到 Riverpod,迁移成本不高,但代码的可维护性提升了一个档次。

4.6 选型对比表

维度 Provider Bloc GetX Riverpod
学习曲线
代码量
测试友好度
团队协作
适合项目规模 中小型 大型 小型/原型 中大型
社区生态 成熟 成熟 活跃 快速增长

4.7 实战建议:我到底该选哪个?

说实话,没有「最好」的状态管理,只有「最合适」的。我根据项目类型给几个建议:

  • 个人练手或小工具:GetX,开发快,代码少
  • 3-5 人团队的中型项目:Riverpod,平衡了简洁和规范
  • 10 人以上的大型企业项目:Bloc,强制规范,方便代码审查
  • 团队新手多,需要快速上手:Provider,文档多,坑少

最后提醒一句:不管你选哪个方案,一定要在项目初期就定下来。我见过太多项目做到一半换状态管理方案,那感觉就像给高速行驶的汽车换轮胎——不是不能换,但代价太大了。

下一章,咱们会基于本章的选型结论,搭建一个完整的项目骨架。到时候我会用 Riverpod 做演示,因为它在企业级项目里确实表现不错。当然,如果你选了 Bloc 或 Provider,核心架构思路是相通的。