Flutter 状态管理选型:Provider、Riverpod、GetIt 对比
目录
- 一、先说结论:它们并不是同一类东西
- 二、Provider:最贴近 Flutter 原生心智
- 三、Riverpod:更现代的响应式状态管理
- 四、GetIt:更像依赖注入容器,不是完整状态管理方案
- 五、核心维度横向对比
- 六、实际项目怎么选
- 七、常见误区
- 八、总结
一、先说结论:它们并不是同一类东西
很多人在 Flutter 里做技术选型时,会把 Provider / Riverpod / GetIt 放在一起比较,但它们其实不完全是同一层的东西。
- Provider:基于
InheritedWidget的状态注入与订阅方案,和 Flutter widget tree 绑定很深 - Riverpod:从 Provider 演化出来的独立响应式状态管理框架,不依赖
BuildContext - GetIt:本质是 Service Locator / 依赖注入容器,负责“拿对象”,不天然负责“驱动 UI 刷新”
所以如果一句话概括:
- Provider 适合简单到中等复杂度应用
- Riverpod 更适合中大型 Flutter 项目
- GetIt 更适合做依赖管理,单独拿来做状态管理通常不够完整
二、Provider:最贴近 Flutter 原生心智
Provider 的核心思路其实很朴素:
- 把状态放到 widget tree 上层
- 通过
Provider往下提供对象 - 通过
Consumer/context.watch订阅变化 - 通过
notifyListeners()触发刷新
它最大的优点是:非常符合 Flutter“状态上提”的原生思维。
class CounterModel extends ChangeNotifier {
int count = 0;
void increment() {
count++;
notifyListeners();
}
}
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: const MyApp(),
)
Consumer<CounterModel>(
builder: (_, model, __) {
return Text('${model.count}');
},
)
Provider 的优点
- 上手成本低,概念少
- 和 Flutter 官方文档教程非常一致
- 对小项目、Demo、后台配置页很友好
- 基于
BuildContext,写法比较直观
Provider 的问题
- 强依赖 widget tree 和
BuildContext - 复杂依赖关系会逐渐变乱
- 异步状态、缓存、错误态需要自己组织
ChangeNotifier是可变状态,规模大时容易失控- 测试和模块解耦能力不如 Riverpod
Provider 适合什么场景
- Flutter 初学者
- 页面不多、状态关系简单的项目
- 团队成员都熟悉
ChangeNotifier - 更强调“够用”和低心智负担,而不是极致可维护性
三、Riverpod:更现代的响应式状态管理
Riverpod 可以理解成“把 Provider 这套思想,从 widget tree 中解耦出来,再做了一次现代化升级”。
它和 Provider 最大的区别有两个:
- 不依赖
BuildContext - 不仅管理状态,还管理依赖、异步、缓存和生命周期
最常见的用法是 ProviderScope + ref.watch/ref.read:
final counterProvider = StateProvider<int>((ref) => 0);
class CounterPage extends ConsumerWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(child: Text('$count')),
floatingActionButton: FloatingActionButton(
onPressed: () {
ref.read(counterProvider.notifier).state++;
},
),
);
}
}
Riverpod 的核心优势
1. 不依赖 BuildContext
这点非常关键。
在 Provider 里,很多读取依赖和状态的动作都发生在 widget tree 中;但在 Riverpod 里,provider 本身就是独立节点。这样做有几个直接收益:
- 业务逻辑不容易和 UI 强绑定
- 更容易单元测试
- 更容易拆模块
- 更适合纯 Dart 层复用
2. 异步状态是一等公民
Riverpod 对异步场景支持非常自然,FutureProvider、StreamProvider、AsyncNotifier 都是围绕异步建模的。
final userProvider = FutureProvider<User>((ref) async {
return api.fetchUser();
});
UI 层天然就是 loading / data / error 三态:
final user = ref.watch(userProvider);
return switch (user) {
AsyncData(:final value) => Text(value.name),
AsyncError(:final error) => Text('error: $error'),
_ => const CircularProgressIndicator(),
};
这比 ChangeNotifier + bool isLoading + Object? error 清晰很多。
3. 依赖关系是显式的
一个 provider 依赖另一个 provider 时,关系写在代码里,而不是散落在构造器、context 或全局单例里。
final apiProvider = Provider<ApiClient>((ref) => ApiClient());
final userRepoProvider = Provider<UserRepository>((ref) {
return UserRepository(ref.watch(apiProvider));
});
这对中大型项目特别重要,因为状态图会逐渐复杂,显式依赖比“哪里都能拿”更容易维护。
4. 生命周期可控
autoDispose、缓存、重建、监听、失效这些能力,Riverpod 都有比较完整的机制。
它不只是“把状态塞给 UI”,而是把“状态什么时候创建、什么时候销毁、什么时候刷新”也纳入了框架。
Riverpod 的代价
- 学习曲线高于 Provider
- provider 种类多,初看会有一点抽象
- 团队如果不统一规范,写法容易分裂
- 引入代码生成后,体验更强,但工程复杂度也会上来
Riverpod 适合什么场景
- 中大型 Flutter 项目
- 异步请求多、状态关系复杂的应用
- 想提高可测试性和可维护性
- 希望业务逻辑和 UI 解耦更干净
四、GetIt:更像依赖注入容器,不是完整状态管理方案
GetIt 的定位和前两个不一样。
它解决的问题不是“状态怎么响应式传播”,而是:
- 某个 service / repository / use case 从哪里拿
- 生命周期怎么管理
- 如何避免层层传参
典型写法如下:
final getIt = GetIt.instance;
void setupDI() {
getIt.registerLazySingleton<ApiClient>(() => ApiClient());
getIt.registerLazySingleton<UserRepository>(
() => UserRepository(getIt<ApiClient>()),
);
}
final repo = getIt<UserRepository>();
GetIt 的优点
- 简单直接,注册一次,全局可取
- 不依赖
BuildContext - 依赖注入体验好,适合 service/repository 层
- 对纯 Dart、CLI、服务端代码也能复用
- 测试替换依赖相对方便
GetIt 的问题
1. 它不是完整状态管理
这是最容易误解的点。
GetIt 只负责“拿对象”,并不天然解决以下问题:
- UI 什么时候刷新
- 加载中/错误态怎么表达
- 状态依赖关系怎么追踪
- 页面销毁后资源怎么自动释放
所以你如果直接用 GetIt + 单例对象 管整个 UI 状态,项目一大很容易变成“全局变量升级版”。
2. 全局访问很爽,但边界容易变模糊
任何地方都能 getIt<T>(),短期开发效率很高;但从长期维护看,也意味着:
- 依赖来源不够显式
- 模块边界容易被打穿
- 阅读代码时不容易一眼看出依赖图
3. 响应式能力通常要靠别的东西补
很多团队实际用法不是“只用 GetIt”,而是:
GetIt + ValueNotifierGetIt + watch_itGetIt + BLoCGetIt + Riverpod
也就是说,GetIt 更像依赖管理底座,而不是单独扛整个状态体系。
GetIt 适合什么场景
- 需要做依赖注入
- 业务层和数据层对象很多
- 希望摆脱
BuildContext取依赖 - 已经有自己的状态管理方案,只是缺一个 DI 容器
五、核心维度横向对比
| 维度 | Provider | Riverpod | GetIt |
|---|---|---|---|
| 定位 | 状态注入 + UI 订阅 | 响应式状态管理 + 依赖管理 | Service Locator / 依赖注入 |
是否依赖 BuildContext | 是 | 否 | 否 |
| 是否天然驱动 UI 刷新 | 是 | 是 | 否 |
| 异步状态支持 | 一般,需要自己封装 | 很强,原生支持 AsyncValue | 弱,需要额外方案 |
| 依赖关系是否显式 | 一般 | 强 | 较弱 |
| 学习成本 | 低 | 中 | 低 |
| 可测试性 | 中 | 高 | 中到高 |
| 适合项目规模 | 小到中 | 中到大 | 作为 DI 可用于各种规模 |
| 常见问题 | ChangeNotifier 膨胀 | 概念偏多 | 滥用全局单例 |
如果只给一句话评价:
- Provider:最容易上手,但上限相对低
- Riverpod:最均衡,工程化能力最强
- GetIt:不是状态管理替代品,而是 DI 工具
六、实际项目怎么选
1. 小项目 / 个人项目 / 快速验证
优先考虑 Provider。
原因很简单:
- 学习成本最低
- 和 Flutter 官方示例一致
- 对几页表单、列表、详情页完全够用
2. 中大型项目 / 长期维护项目
优先考虑 Riverpod。
尤其是下面这些情况:
- 页面多
- 接口多
- 缓存和异步逻辑复杂
- 团队多人协作
- 很重视测试
这类项目里,Riverpod 的优势会越来越明显。
3. 你只是想解决依赖注入
那就用 GetIt,但不要指望它一个包解决所有状态问题。
比较合理的定位是:
GetIt负责 service/repository/usecase 注入- 状态仍然交给 Provider / Riverpod / BLoC 这类方案
4. 我个人更推荐的组合
如果是今天新开一个 Flutter 中大型项目,我更倾向于:
- 首选:Riverpod
- 可选:Riverpod + GetIt(二选一也行)
为什么说 “Riverpod + GetIt 可选”?
因为 Riverpod 本身已经能做依赖管理了,很多项目里其实不一定还需要 GetIt。
如果再叠一层 GetIt,收益未必大,但复杂度会增加。
所以更常见的建议是:
- 简单点:直接全用 Riverpod
- 老项目迁移:保留 GetIt 做 DI,上层逐步换 Riverpod
七、常见误区
误区 1:GetIt 比 Provider/Riverpod 更高级
不是。
它们不是“高级版/低级版”的关系,而是不同定位。
GetIt 解决的是对象获取,Provider/Riverpod 解决的是响应式状态传播。
误区 2:Provider 已经过时
也不是。
Provider 依然很好用,尤其适合入门和简单项目。
只是当项目复杂度上来后,Riverpod 往往更容易维护。
误区 3:Riverpod 一定要代码生成
不一定。
代码生成会让体验更完整,但不是必须。
只是如果项目已经走向中大型,通常最后都会逐渐用上。
误区 4:一个项目里绝不能混用
也不绝对。
现实里很多项目会是:
- 历史代码用 Provider
- 新模块用 Riverpod
- 基础设施层用 GetIt
混用不是原罪,没有边界和规范才是问题。
八、总结
如果你只想记住最核心的选型建议,可以直接记这三句:
- 新手、小项目:Provider
- 中大型、长期维护:Riverpod
- 依赖注入:GetIt,但别把它当完整状态管理
从 2026 年的 Flutter 生态来看,如果你问我“新项目最稳妥的默认答案是什么”,我的答案会是:
优先用 Riverpod。
它在可维护性、异步建模、测试友好、UI 解耦这些方面,整体平衡是最好的。
Provider 依然适合简单项目;GetIt 依然适合做 DI;但如果只能选一个更适合长期演进的状态体系,Riverpod 通常会是更稳的选择。