Flutter 状态管理选型: Provider / Riverpod / Bloc / GetX 怎么选

Flutter 状态管理这个话题, 社区吵了好几年也没定论。多数讨论有两个毛病: 只聊 API 哪个优雅, 不聊项目跑半年后好不好维护; 只聊自己喜欢什么, 不聊团队、业务复杂度、生态稳定性这些现实问题。这篇不比”哪个写起来爽”, 比的是”哪个能在真实项目里活下来”。

一、先把问题掰直: 状态管理到底在管什么

很多人一上来就对比 setState / notifyListeners / ref.watch / emit / Obx——这是表象。状态管理真正要解决的, 是这五件事:

  1. 状态放在哪里
  2. 状态变化后, 谁来通知 UI
  3. 异步请求、空状态、错误状态怎么建模
  4. 页面越来越复杂时, 代码会不会失控
  5. 团队里第二个人接手, 还看不看得懂

大家都能写出一个能跑的页面。真正拉开差距的, 是当业务变成”列表 + 刷新 + 请求失败 + 搜索 + 筛选 + 排序 + 弹 Toast/Dialog/跳详情”这种组合时, 这套结构还能不能顶住。

状态管理的核心, 不是”能不能更新 UI”, 而是”当 UI 和业务越来越复杂时, 这套结构还能不能稳”。这是选型的真正维度。

二、Provider: Flutter 状态管理里的”老实人”

基于 InheritedWidget 封装, Google 官方推荐, 概念最直白: 状态在 ViewModel 里, 用 notifyListeners 通知, Consumer 监听。

class CounterViewModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  void increment() {
    _count++;
    notifyListeners();
  }
}

// 注入
ChangeNotifierProvider(
  create: (_) => CounterViewModel(),
  child: Consumer<CounterViewModel>(
    builder: (_, vm, __) => Text('${vm.count}'),
  ),
);

优点: 官方背书, 生态成熟, 文档丰富, 新人上手快, 面试常问。

代价: 依赖 BuildContext, 异步方法里拿不到 context 是经典痛点; 样板代码多, ChangeNotifier + Consumer 写到手抽; 多个相互依赖的状态会缠在一起。

适合: 中小项目、入门、单人快速起步。

三、Riverpod: Provider 的究极进化

Provider 作者 Remi 自己重写的方案, 解决了 Provider 的几个硬伤: 无 context 限制、编译期类型安全、不依赖 Flutter 树。

final counterProvider = NotifierProvider<CounterNotifier, int>(CounterNotifier.new);

class CounterNotifier extends Notifier<int> {
  @override
  int build() => 0;
  void increment() => state++;
}

// 任意位置读写, 不需要 context
class CounterView extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Text('$count');
  }
}

优点: 无 context, 能在任意位置读写; 编译期类型安全, 错误提前暴露; 支持代码生成 (riverpod_generator), 减少样板; 可测试性极强。

代价: 概念多 (Provider/Family/Notifier/AsyncNotifier…), 初学会被一堆命名搞晕; 心智成本比 Provider 高一档。

适合: 中大型新项目、需要高可测试性、强调类型安全。这是我目前最愿意推荐的平衡方案。

四、Bloc / Cubit: 企业级钢铁堡垒

强制单向数据流, Event → Bloc → State, UI 和逻辑完全解耦。闲鱼、宝马等大厂长期使用。

// 事件
sealed class CounterEvent {}
class Increment extends CounterEvent {}

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

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

// UI
BlocBuilder<CounterBloc, CounterState>(
  builder: (_, state) => Text('${state.count}'),
);

Cubit 是 Bloc 的简化版, 省掉 Event, 直接调方法 emit 新状态, 适合不需要事件溯源的场景。

优点: 架构最规范, UI 与逻辑完全解耦; 可测试性最强, 配合 bloc_test 能精确断言状态序列; BlocObserver 能全局追踪所有状态流转, 调试利器; 多人协作有标准可循。

代价: 样板代码多, 写个计数器要定义 Event/State/Bloc 三件套; 上手成本高; 简单业务用 Bloc 是杀鸡用牛刀。

适合: 大型团队、多人协作、强调规范和状态流可追踪的中大型项目。

五、GetX: 瑞士军刀, 也是维护黑洞

API 极简, 一把梭: 状态、路由、依赖注入、国际化全打包。

class CounterController extends GetxController {
  var count = 0.obs;
  void increment() => count++;
}

// 页面
Obx(() => Text('${controller.count.value}'));

优点: 写起来是真的爽, 代码量极少; 不需要 context; 学习成本极低, 新人十分钟起飞。

代价: 全局单例黑魔法, 脱离 Flutter 原生机制; 状态在哪更新全靠自觉, 项目大了是维护黑洞; 测试不友好; 接盘风险极高——GetX 项目交接给别人约等于友尽。

适合: 外包、毕设、个人快速出活。新项目再从 0 选 GetX, 我会明显更谨慎。

六、同一场景的代码量对比

实现同一个计数器, 不含注释:

方案行数样板感类型安全context 依赖
GetX~20极低运行时
Provider~30运行时
Riverpod~35中低(代码生成后更低)编译期
Bloc~50编译期

代码量小不等于好维护。GetX 写得最快, 维护最痛; Bloc 写得最慢, 接盘最稳。

七、我的选型框架

按项目规模和团队两个维度:

  • 小项目 / Demo / 个人: ProvidersetState
  • 中小型新项目: Riverpod(平衡方案, 我个人首选)
  • 大型团队 / 多人协作 / 强规范: BlocCubit
  • 老项目已深度绑定 GetX: 先稳住, 别一激动全量重构; 新模块可逐步切 Riverpod
  • 新项目从 0 选 GetX: 谨慎, 除非是真的短期外包

八、关于 GetX 删库风波: 生态稳定也是选型维度

GetX 作者账户被风控、仓库一度不可用那波, 把一个以前很多人不愿正视的问题硬生生摊开了: 依赖生态的稳定性, 也是技术选型的一部分。

以前大家觉得”能跑就行”, 那次才意识到: 你今天选一个方案, 不只是选它今天写起来爽不爽, 还在选三个月后好不好改、半年后新人能不能接、一年后团队还能不能稳稳维护、真出事时有没有兜底能力。

选型要看四条: 解决什么问题、边界在哪、为什么有人爱也有人骂、生态是否稳定。只看第一条的人, 选出来的方案多半活不过一年。

九、关注 Signals: 官方实验性响应式

signals 是 Flutter 官方在做的实验性响应式 API, 思路类似 Vue 的 ref 或 SolidJS 的 signal, 无三方依赖, 自动追踪依赖。目前还在实验阶段, 生态和文档都少, 但方向值得跟踪——如果未来稳定, 可能是 Flutter 原生的”现代状态管理”答案。

能在选型时提一句”我在关注 Signals 这个官方实验方向”, 体现你不是只会用现成库, 而是在跟进演进。

小结

没有”最好”的状态管理, 只有”适合”的。选型的真正标准不是 API 优雅度, 而是这套结构在你的项目规模、团队配置、维护周期下能不能稳。Provider 是老实人, Riverpod 是现代平衡方案, Bloc 是企业堡垒, GetX 写着爽但维护痛。先想清楚你的项目和团队, 再选库, 别反过来。