原文链接:Combining requests | Riverpod
pub:riverpod | Dart Package (flutter-io.cn)
译时版本: 2.4.9
之前翻译过 Riverpod 的官方文档,现在随着版本更新,官方文档又多了很多新内容,所以再补充翻译一下。
之前翻译过的内容,现在官方文档有中文了。
Flutter状态管理库Riverpod官方文档翻译汇总 - 掘金 (juejin.cn)
绑定请求
迄今为止,只看到了相互独立请求的场景。但是一个常见的使用场景是需要基于另一个请求的结果触发某个请求。
可以 使用 向请求传递参数 [中文]机制来实现,把一个 provider 的结果作为参数传递给另一个 provider 。
但是该方式有一些缺点:
- 这会泄漏实现细节。现在,UI 需要知道所有使用了其它 provider 的 provider 。
- 无论只要参数发生改变,就会创建一个全新的状态。对于传递参数这种方式,当参数改变时,没有保持前一次状态的方法。
- 绑定请求更加困难。
- 相关工具不再适用。devtool 不会知道 provider 之间的关系。
要改善该问题,Riverpod 提供了不同的方式绑定请求。
基础用法:获取 "ref"
绑定请求的所有可能方式有一个共通点:它们都基于 Ref
对象。
Ref
对象是所有 provider 都能访问的对象。
它赋予它们对多种生命周期监听器的访问权限,也有多种绑定 provider 的方法。
在哪里能获取到 Ref
取决于 provider 的类型。
在函数式的 provider 里,Ref
是作为参数传递给 provider 的函数:
dart
@riverpod
int example(ExampleRef ref) {
// "Ref" 在这里可用来读取其它 provider
final otherValue = ref.watch(otherProvider);
return 0;
}
在类变量中,Ref
是 Notifier 类的一个属性:
dart
@riverpod
class Example extends _$Example {
@override
int build() {
// "Ref" 在这里可用来读取其它 provider
final otherValue = ref.watch(otherProvider);
return 0;
}
}
使用 ref 读取 provider
ref.watch
方法
现在获取到了 Ref
,可以用它来绑定请求。主要方式是使用 ref.watch
。
通常建议构建代码,这样可以使用 ref.watch
而不是其它选项,因为通常它更易于维护。
ref.watch
接收一个 provider 并返回它的当前状态。然后,无论何时被监听的 provider 发生改变时,provider 会失效然后在下一个状态或下一次读取时重新构建。
使用 ref.watch
,业务逻辑会同时变成"响应式"和"声明式"。
这意味着业务逻辑会在需要时自动重新计算。并且更新机制也不依赖副作用,如 "on change" 。这和 StatelessWidget 的行为是类似的。
作为示例,定义一个 provider 监听用户的位置。然后使用该位置获取用户附近的餐厅列表。
dart
@riverpod
Stream<({double longitude, double latitude})> location(LocationRef ref) {
// TO-DO: 返回获取当前位置的 Stream (流)
return someStream;
}
@riverpod
Future<List<String>> restaurantsNearMe(RestaurantsNearMeRef ref) async {
// 使用 "ref.watch" 获取最新的位置。
// 在 provider 后指定 ".future" ,代码会等待直到至少有一个位置可以获取到。
final location = await ref.watch(locationProvider.future);
// 现在可以创建基于位置的网络请求。
// 例如,使用 Google Map API :
// https://developers.google.com/maps/documentation/places/web-service/search-nearby
final response = await http.get(
Uri.https('maps.googleapis.com', 'maps/api/place/nearbysearch/json', {
'location': '${location.latitude},${location.longitude}',
'radius': '1500',
'type': 'restaurant',
'key': '<your api key>',
}),
);
// 从 JSON 中获取餐厅名
final json = jsonDecode(response.body) as Map;
final results = (json['results'] as List).cast<Map<Object?, Object?>>();
return results.map((e) => e['name']! as String).toList();
}
信息
被监听的 provider 改变时,请求会重新计算,前一个状态会保持到新的请求完成。
同时,请求挂起时, "isLoading" (加载中)和 "isReloading" (重新加载中)标志会被设定。
这使 UI 能显示前一次的状态,或者加载中指示器,或者二者同时显示。
信息注意,这里使用了
ref.watch(locationProvider.future)
代替ref.watch(locationProvider)
。这是因为locationProvider
是异步的。因此需要等到初始值可用。如果省略了这里的
.future
,会接收到一个AsyncValue
,它是locationProvider
当前状态的一个快照。但是如果还没有可用的位置,我们也无法做其它任何处理。
警告在"命令式"执行的代码中调用
ref.watch
被认为是不好的实践。这意味着在 provider 的构建阶段所有代码都可能不会被执行。包括 "listener" 回调或者用于 Notifier 的方法:
dart@riverpod int example(ExampleRef ref) { ref.watch(otherProvider); // 好的写法! ref.onDispose(() => ref.watch(otherProvider)); // 不好的写法! final someListenable = ValueNotifier(0); someListenable.addListener(() { ref.watch(otherProvider); // 不好的写法! }); return 0; } @riverpod class MyNotifier extends _$MyNotifier { @override int build() { ref.watch(otherProvider); // 好的写法! ref.onDispose(() => ref.watch(otherProvider)); // 不好的写法! return 0; } void increment() { ref.watch(otherProvider); // 不好的写法! } }
ref.listen
/listenSelf
方法
ref.listen
方法是 ref.watch
的替代方案。
它和传统的 "listen"/"addListener" 方法类似。接收一个 provider 和 回调,在 provider 的内容发生改变时调用声明的回调。
通常建议重构代码这样就可以使用 ref.watch
代替 ref.listen
,因为后者由于它的命令式特征更容易出错。
但是在一些简短无需重大重构的业务逻辑中 ref.listen
比较有用。
可以用 ref.listen
重写 ref.watch
示例
dart
@riverpod
int example(ExampleRef ref) {
ref.listen(otherProvider, (previous, next) {
print('Changed from: $previous, next: $next');
});
return 0;
}
信息
在 provider 的构建阶段使用
ref.listen
是完全安全的。如果 provider 不知道什么原因重新计算了,前一个 listener (监听者)会被移除。作为替代方案,如果需要,可以使用
ref.listen
的返回值手动清除 listener (监听者)。
ref.read
方法
最后一个可用项是 ref.read
。和 ref.watch
类似,它里面也返回 provider 的当前状态。 但是不像 ref.watch
,它不会监听 provider 。
因此,ref.read
应该只在无法使用 ref.watch
的地方作为替换,如在 Notifier 的方法内部。
dart
@riverpod
class MyNotifier extends _$MyNotifier {
@override
int build() {
// 不好的写法! 这里不要使用 "read" ,因为它不是响应式的。
ref.read(otherProvider);
return 0;
}
void increment() {
ref.read(otherProvider); // 这里可以使用 "read"
}
}
警告
对 provider 使用
ref.read
需要小心,因为它不会监听 provider ,被调用的 provider 可能会在不被监听时决定清除它的状态。