这是一篇关于使用 riverpod_generator 实现 Riverpod "Family"(传参)功能的系统化整理文章。
Riverpod Generator 实战:系统化掌握 Family(传参)模式
在 Riverpod 中,Family 是一个核心概念,它允许我们向 Provider 传递参数,从而创建基于参数的动态状态(例如:根据 userID 获取用户信息)。
在使用 riverpod_generator(代码生成器)时,Family 的使用方式变得更加直观和类似于普通函数调用,彻底解决了旧版本中“只能传递一个参数”的痛点。本文将系统地介绍如何在代码生成模式下使用 Family。
1. 核心概念:什么是 Family?
可以将 Family 想象成一个 Map(映射):
- Key(键):你传递的参数(如 ID、搜索关键词)。
- Value(值):该参数对应的 Provider 状态。
当你请求 userProvider(id: 1) 和 userProvider(id: 2) 时,Riverpod 会分别创建并缓存两个独立的状态。
2. 基础用法:函数式 Provider (Functional)
对于只读的、无副作用的 Provider(通常对应 FutureProvider 或 Provider),我们通过在函数中添加参数来实现 Family。
定义 Provider
只需在函数签名中添加所需的参数即可,生成器会自动处理 .family 的逻辑。
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'user_provider.g.dart';
// 定义一个接受 int 类型 userId 的 Provider
@riverpod
Future<User> fetchUser(FetchUserRef ref, int userId) async {
// 模拟网络请求
final json = await http.get('api/user/$userId');
return User.fromJson(json);
}
在 UI 中使用
使用时,直接像调用函数一样传递参数:
@override
Widget build(BuildContext context, WidgetRef ref) {
// 监听特定 userId 的状态
final userAsync = ref.watch(fetchUserProvider(123));
return userAsync.when(
data: (user) => Text(user.name),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
);
}
3. 进阶用法:类 Provider (Notifier / AsyncNotifier)
对于需要修改状态或包含业务逻辑的 Provider(通常对应 Notifier 或 AsyncNotifier),参数是通过 build 方法 传递的。
定义 Notifier
在 build 方法中定义参数,生成器会自动将这些参数注入到生成的 Provider 中。
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'message_notifier.g.dart';
@riverpod
class MessageNotifier extends _$MessageNotifier {
// build 方法接受参数 chatroomId
@override
Future<List<Message>> build(String chatroomId) async {
return _fetchMessages(chatroomId);
}
// 类内部的方法可以直接通过 this.chatroomId 访问参数(由生成器生成)
Future<void> sendMessage(String text) async {
await http.post('api/chat/${this.chatroomId}', body: text);
// 刷新状态
ref.invalidateSelf();
}
}
使用方式
// 获取特定聊天室的 Notifier
ref.read(messageNotifierProvider('room_001').notifier).sendMessage('Hello');
4. 解决痛点:传递多个参数
在旧版 Riverpod 中,Family 仅限传递一个位置参数,若需传递多个参数,不得不使用 Tuple 或自定义类。
使用 riverpod_generator,这一限制被彻底解除。 你可以使用任意数量的参数,包括位置参数、命名参数、可选参数和默认值。
示例:多参数与命名参数
@riverpod
Future<List<Product>> products(
ProductsRef ref, {
required String category,
int page = 1, // 支持默认值
String? sortOrder, // 支持可选参数
}) async {
return await http.get('api/products?cat=$category&page=$page&sort=$sortOrder');
}
调用更加优雅
ref.watch(productsProvider(category: 'electronics', page: 2));
5. 关键注意事项
为了确保 Riverpod 的缓存机制正常工作,传递给 Family 的参数必须遵循以下规则:
- 一致性 (Equality):
- 参数对象必须正确重写
==和hashCode。 - 基本类型(int, String, List 等)通常没问题,但自定义类必须实现值相等性(推荐使用
freezed或equatable包)。 - 如果两个对象的
==返回true,Riverpod 就会复用同一个状态,而不会重新创建。
- 参数对象必须正确重写
- 不要直接传递 UI 对象:
- 避免将
BuildContext或AnimationController等 UI 相关对象作为参数传递,这会导致内存泄漏或不稳定的行为。
- 避免将
- AutoDispose 默认开启:
- 使用
riverpod_generator生成的 Provider 默认都是autoDispose的。即当不再有监听者时,状态会自动销毁。 - 如果需要保持状态,请设置
@Riverpod(keepAlive: true)。
- 使用
总结
使用 riverpod_generator 实现 Family 功能带来了质的飞跃:
| 特性 | 旧版手动定义 | 新版 Generator (@riverpod) |
|---|---|---|
| 参数数量 | 仅限 1 个 | 无限,支持命名、可选参数 |
| 定义位置 | .family 修饰符 |
函数参数 或 build() 方法参数 |
| 类型安全 | 需手动指定泛型 | 自动推导 |
| 代码量 | 较繁琐 | 简洁清晰 |
这种方式不仅减少了样板代码,还让 Provider 的定义更贴近 Dart 原生函数的写法,极大地提高了代码的可读性和维护性。