Riverpod 3.0:一个过度设计的反面教材

前言


本分析基于在生产应用中使用 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}');
  }
}

问题显而易见:

  1. context 并没有消失,还是要传进来
  2. ref 替代了 context,但代码里依然到处都是 ref.watch()
  3. ❌ 你需要记住哪些用 ref.watch(),哪些用 ref.read(),哪些用 ref.listen()
  4. ref 本质上和 context 一样,都是一个"全局服务定位器"的引用

二、功能泛滥:超出状态管理的职责

2.1 状态管理应该做什么?

回到本质,状态管理的核心职责应该是:

  1. 持有状态(State)
  2. 更新状态(setState / notifyListeners)
  3. 通知监听者(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...

相关推荐
奋斗的小青年!!5 小时前
Flutter浮动按钮在OpenHarmony平台的实践经验
flutter·harmonyos·鸿蒙
程序员老刘8 小时前
一杯奶茶钱,PicGo + 阿里云 OSS 搭建永久稳定的个人图床
flutter·markdown
奋斗的小青年!!11 小时前
OpenHarmony Flutter 拖拽排序组件性能优化与跨平台适配指南
flutter·harmonyos·鸿蒙
小雨下雨的雨12 小时前
Flutter 框架跨平台鸿蒙开发 —— Stack 控件之三维层叠艺术
flutter·华为·harmonyos
行者9613 小时前
OpenHarmony平台Flutter手风琴菜单组件的跨平台适配实践
flutter·harmonyos·鸿蒙
小雨下雨的雨15 小时前
Flutter 框架跨平台鸿蒙开发 —— Flex 控件之响应式弹性布局
flutter·ui·华为·harmonyos·鸿蒙系统
cn_mengbei15 小时前
Flutter for OpenHarmony 实战:CheckboxListTile 复选框列表项详解
flutter
cn_mengbei15 小时前
Flutter for OpenHarmony 实战:Switch 开关按钮详解
flutter
奋斗的小青年!!15 小时前
OpenHarmony Flutter实战:打造高性能订单确认流程步骤条
flutter·harmonyos·鸿蒙
Coder_Boy_15 小时前
Flutter基础介绍-跨平台移动应用开发框架
spring boot·flutter