前言
本分析基于在生产应用中使用 Riverpod 的实际经验。目标不是贬低 Riverpod 团队的辛勤工作,而是鼓励对软件设计决策的批判性思考,并展示更简单的替代方案如何更有效。
Riverpod 3.0 作为 Flutter 社区备受推崇的状态管理方案,被许多开发者视为"最佳实践"。然而,当我们深入分析其设计理念和实际使用体验后,会发现这是一个典型的过度设计案例。本文将从架构设计、语义一致性、代码可读性等多个维度,剖析 Riverpod 3.0 的设计缺陷。
一、抛弃了 context
,却引入了 ref
:换汤不换药
1.1 Riverpod 的宣传口号
Riverpod 官方文档中反复强调的一个优势是:
"不依赖 BuildContext,可以在任意位置访问 Provider"
这听起来很美好,似乎解决了 Provider 的痛点。但实际上呢?
1.2 context
的"屎"变成了 ref
的"屎"
dart
// ===== 使用 Provider(基于 context)=====
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = context.watch<Counter>(); // context 拉屎
final user = context.watch<User>(); // context 拉屎
final theme = context.watch<ThemeData>(); // context 拉屎
return Text('${counter.count}');
}
}
// ===== 使用 Riverpod(基于 ref)=====
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) { // 看,context 还在!
final counter = ref.watch(counterProvider); // ref 拉屎
final user = ref.watch(userProvider); // ref 拉屎
final theme = ref.watch(themeProvider); // ref 拉屎
return Text('${counter.count}');
}
}
问题显而易见:
- ❌
context
并没有消失,还是要传进来 - ❌
ref
替代了context
,但代码里依然到处都是ref.watch()
- ❌ 你需要记住哪些用
ref.watch()
,哪些用ref.read()
,哪些用ref.listen()
- ❌
ref
本质上和context
一样,都是一个"全局服务定位器"的引用
二、功能泛滥:超出状态管理的职责
2.1 状态管理应该做什么?
回到本质,状态管理的核心职责应该是:
- 持有状态(State)
- 更新状态(setState / notifyListeners)
- 通知监听者(Listeners)
就这三件事!
看看 Riverpod 3.0 的功能列表:
dart
✓ 状态管理 ← 核心职责
✓ 依赖注入 ← 额外功能
✓ 自动重试 ← 网络层的责任
✓ 离线缓存 ← 数据层的责任
✓ 暂停/恢复 ← 业务逻辑层的责任
✓ Mutation 管理 ← 表单层的责任
✓ 错误处理 ← 业务逻辑层的责任
这是一个典型的"大而全"框架,试图包办一切。
2.3 案例:自动重试功能
dart
// ===== Riverpod 把重试逻辑放在状态管理层 =====
@Riverpod(
retry: (retryCount, error) {
if (retryCount > 3) return null;
return Duration(seconds: retryCount * 2); // 指数退避
},
)
Future<User> user(Ref ref, String userId) async {
final response = await http.get(Uri.parse('/api/user/$userId'));
return User.fromJson(jsonDecode(response.body));
}
// ❌ 问题:
// 1. 重试逻辑和状态管理混在一起
// 2. 无法复用重试逻辑(其他地方要重新写)
// 3. 难以单独测试重试策略
// 4. 违背单一职责原则
正确的做法:重试逻辑应该在网络层
dart
// ===== 网络层:处理重试 =====
class ApiClient {
final http.Client client;
final RetryPolicy retryPolicy;
ApiClient({
required this.client,
required this.retryPolicy,
});
Future<Response> get(Uri uri) async {
return retryPolicy.execute(() => client.get(uri));
}
}
// ===== 可复用的重试策略 =====
class ExponentialBackoffRetryPolicy implements RetryPolicy {
final int maxRetries;
ExponentialBackoffRetryPolicy({this.maxRetries = 3});
@override
Future<T> execute<T>(Future<T> Function() action) async {
int attempt = 0;
while (true) {
try {
return await action();
} catch (e) {
attempt++;
if (attempt >= maxRetries) rethrow;
await Future.delayed(Duration(seconds: attempt * 2));
}
}
}
}
// ===== Repository:使用网络层 =====
class UserRepository {
final ApiClient apiClient;
UserRepository(this.apiClient);
Future<User> getUser(String userId) async {
// ✅ 重试由 ApiClient 处理,Repository 不关心
final response = await apiClient.get(
Uri.parse('/api/user/$userId'),
);
return User.fromJson(jsonDecode(response.body));
}
}
// ===== ViewModel:只管理状态 =====
class UserViewModel extends ViewModel {
final UserRepository repository;
UserViewModel(this.repository);
Future<void> loadUser(String userId) async {
_isLoading = true;
notifyListeners();
try {
// ✅ ViewModel 不知道重试的存在,只负责状态
_user = await repository.getUser(userId);
} catch (e) {
_error = e.toString();
} finally {
_isLoading = false;
notifyListeners();
}
}
}
ConsumerWidget
:语义矛盾
dart
// ===== Riverpod 的 ConsumerWidget =====
class CounterDisplay extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider); // 监听状态变化
return Text('$counter'); // 状态变化时重建
}
}
// ❓ 等等,这不对啊:
// 1. ConsumerWidget 继承自 StatelessWidget
// 2. 但它会因为状态变化而重建
// 3. 这违背了 "Stateless" 的语义!
// 源码印证:
abstract class ConsumerWidget extends StatelessWidget {
const ConsumerWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref); // 多了个 ref 参数
@override
ConsumerStatefulElement createElement() {
// 💩 实际上创建的是 StatefulElement!
return ConsumerStatefulElement(this);
}
}
这是一个彻头彻尾的语义欺骗!
- ❌ 名字叫
StatelessWidget
,但行为像StatefulWidget
- ❌ 实际创建的是
ConsumerStatefulElement
(有状态的 Element) - ❌ 会因为外部状态变化而重建
- ❌ 新手会被误导:"Stateless 怎么还能更新?"
包括这个作者另一个库:flutter_hooks 也是篡改了 flutter StatelessWidget 语义,到处拉屎。Widget 体系不适合 hooks 机制,强行匹配最终代码会变成火葬场。
ConsumerWidget 侵入性太强
dart
// ===== Riverpod:强制使用 ConsumerWidget =====
// 每个需要状态的 Widget 都被强制改造
// 原本的 StatelessWidget
class ProductCard extends StatelessWidget {
final Product product;
const ProductCard({required this.product});
@override
Widget build(BuildContext context) {
return Card(child: Text(product.name));
}
}
// ❌ 需要访问 Provider?必须改成 ConsumerWidget
class ProductCard extends ConsumerWidget {
final Product product;
const ProductCard({required this.product});
@override
Widget build(BuildContext context, WidgetRef ref) {
final cart = ref.watch(cartProvider); // 为了这一行
return Card(
child: Column(
children: [
Text(product.name),
ElevatedButton(
onPressed: () => cart.add(product),
child: Text('Add to Cart'),
),
],
),
);
}
}
// 💩 整个 Widget 树被 ConsumerWidget 污染
学习曲线
dart
// ===== 新人加入团队 =====
// 使用 Riverpod 的项目:
// Day 1: "Provider 是什么?"
// Day 3: "Ref 是什么?"
// Day 4: "@riverpod 注解生成了什么?"
// Day 5: "为什么我要继承 _$Counter?"
// Day 6: "AsyncValue 怎么用?"
// Day 7: "ref.watch 和 ref.read 的区别?"
// Day 8: "ref.listen 又是什么?"
// Day 9: "Family 是干嘛的?"
// Day 10: "AutoDispose 什么时候用?"
// ref.invalidate() 的作用,会导致什么后果
最致命的问题:架构债务

总结

好的状态管理应该是什么样?
dart
// ✅ 好的状态管理(view_model)
特点: 1. 职责单一:只管理状态
2. 显式依赖:通过构造函数注入
3. 分层清晰:与业务逻辑分离
4. 无魔法:代码即文档
5. 轻量级:不绑定生态系统
6. 易测试:每层独立测试
7. 易维护:代码结构清晰
8. 团队友好:学习曲线平缓
view_model 并不完美,但它做好了最核心的事:状态管理 。其他的职责,交给专门的工具去做。 这才是软件工程的智慧:不要试图用一个工具解决所有问题。
change to pub.dev/packages/vi...