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...

相关推荐
消失的旧时光-19435 小时前
Flutter 并发编程全解:从零掌握 Isolate
flutter
西西学代码13 小时前
Flutter---EQ均衡器
flutter
LinXunFeng18 小时前
Flutter webview 崩溃率上升怎么办?我的分析与解决方案
flutter·ios·webview
西西学代码20 小时前
Flutter---GridView+自定义控件
flutter
hweiyu001 天前
Flutter零基础极速入门到进阶实战(视频教程)
flutter
hweiyu001 天前
Flutter高级进阶教程(视频教程)
flutter
SoaringHeart2 天前
Flutter封装:原生路由管理极简封装 AppNavigator
前端·flutter
疯笔码良3 天前
【Flutter】flutter安装并在Xcode上应用
flutter·macos·xcode