Provider 的“家族”:Family
一句话总结
“Family” 是 Riverpod 最强大的功能之一,它让一个 Provider 能同时管理多个独立的状态。
“Family” (家族) 是 Riverpod 最强大的功能之一。
简单来说,它允许一个 Provider 根据一个独特的参数,关联到多个相互独立的状态。
一个典型的应用场景就是从服务器获取数据,比如根据用户 ID 获取用户信息、根据关键词获取搜索结果、或者根据页码获取列表数据。在这些场景下,响应的数据都依赖于一个或多个参数。Family 让你只需定义一个 Provider,就能搞定所有不同参数下的数据获取和缓存。
小贴士
如果说普通的 Provider 像一个“变量”,那么带 Family 的 Provider 就像一个
Map(字典)。
如何创建一个 Family
定义一个 Family,只需要对 Provider 的定义稍作修改,让它能接收一个参数即可。
对于函数式的 Provider,语法如下:
@riverpod
Future<User> user(
Ref ref,
// 使用代码生成时,provider 可以接收任意数量的参数。
// 这些参数可以是位置参数/命名参数,也可以是必选/可选的。
String id,
) async {
final dio = Dio();
final response = await dio.get('https://api.example.com/users/$id');
return User.fromJson(response.data);
}对于 Notifier 类型的 Provider,语法是这样的:
@riverpod
class UserNotifier extends _$UserNotifier {
@override
Future<User> build(
// 使用代码生成时,Notifier 可以在 "build" 方法上定义参数。
// 可以定义任意数量的参数。
String id,
) async {
final dio = Dio();
final response = await dio.get('https://api.example.com/users/$id');
// 生成的类中,可以自然地通过 "this" 访问到传递给 "build" 方法的参数:
print(this.id);
return User.fromJson(response.data);
}
}小贴士
虽然不是强制性的,但我们强烈建议在使用 Family 时,开启自动销毁功能。
这样可以避免当参数改变,旧的状态不再需要时,发生内存泄漏。
如何使用一个 Family
带参数的 Provider,在使用时也需要稍作改变。
长话短说,你需要像调用函数一样,把你的 Provider 期望的参数传给它:
final user = ref.watch(userProvider('123'));注意
传递的参数需要有一个稳定可靠的
==和hashCode实现。你可以把 “Family” 看作一个
Map,其中参数是key,Provider 的状态是value。因此,如果参数的==/hashCode变了,你得到的值也会不同。所以,像下面这样的代码是错误的:
dart// 错误的参数,因为 `[1, 2, 3] != [1, 2, 3]` ref.watch(myProvider([1, 2, 3]));为了帮助发现这个错误,建议使用 riverpod_lint 并开启 provider_parameters 这条 lint 规则。这样,上面的代码片段就会显示一个警告。安装步骤请参见入门指南。
你可以读取任意多个带 Family 的 Provider,它们之间都是相互独立的。所以,这样做是完全合法的:
class Example extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final user1 = ref.watch(userProvider('123'));
final user2 = ref.watch(userProvider('456'));
// user1 和 user2 是相互独立的。
}
}在测试中覆盖 Family
在写测试时,你可能想要模拟一个带 Family 的 Provider。
在这种情况下,你有两个选择:
只覆盖一个特定的参数组合:
dartawait tester.pumpWidget( ProviderScope( overrides: [ // 只模拟 id 为 '123' 的情况 userProvider('123').overrideWith((ref) => User(name: 'User 123')), ], child: const MyApp(), ), );覆盖所有的参数组合:
dartawait tester.pumpWidget( ProviderScope( overrides: [ // 模拟所有 id 的情况 userProvider.overrideWith((ref, id) => User(name: 'User $id')), ], child: const MyApp(), ), );