组合请求
到目前为止,我们只看到请求彼此独立的案例。 但一个常见的用例是必须根据另一个请求的结果触发请求。
我们可以使用将参数传递给您的请求机制来做到这一点, 方法是将一个提供者程序的结果作为参数传递给另一个提供者程序。
但这种方法有一些缺点:
- 这泄露了实现细节。 现在,UI 需要了解所有被其他提供者程序使用的提供者程序。
- 每当参数发生变化时,都会产生一个全新的状态。 通过传递参数,当参数更改时,无法保持以前的状态。
- 它使合并请求变得更加困难。
- 这使得开发工具的用处降低。devtool 不会知道提供者程序之间的关系。
为了改善这一点,Riverpod 提供了一种不同的方法来合并请求。
基础知识:获取 "ref"
组合请求的所有可能方法都有一个共同点:它们都基于 Ref
对象。
该 Ref
对象是所有提供者程序都有权访问的对象。
它允许他们访问各种生命周期监听器,
还可以使用各种方法来组合提供者程序。
可以获取的位置 Ref
取决于提供者程序的类型。
在函数提供者程序中,将 Ref
作为参数传递给提供者程序的函数:
int example(Ref ref) {
// "Ref" can be used here to read other providers
final otherValue = ref.watch(otherProvider);
return 0;
}
在类变体中,Ref
是通知者程序类的一个属性:
class Example extends _$Example {
int build() {
// "Ref" can be used here to read other providers
final otherValue = ref.watch(otherProvider);
return 0;
}
}
使用 ref 读取提供者程序。
ref.watch
方法。
现在我们已经获得了一个 Ref
,我们可以用它来组合请求。
执行此操作的主要方法是使用 ref.watch
。
通常建议对代码进行架构设计,
以便可以使用 ref.watch
的其他选项,因为它通常更易于维护。
该 ref.watch
方法采用提供者程序,并返回其当前状态。
然后,每当监听的提供者程序发生更改时,我们的提供者程序将在
下一帧或下一次读取时失效并重新生成。
通过使用 ref.watch
,您的逻辑变得既是“反应式”又是“声明式”。
这意味着您的逻辑将在需要时自动重新计算。
并且更新机制不依赖于副作用,例如“更改”。
这类似于 StatelessWidgets 的行为方式。
例如,我们可以定义一个监听用户位置的提供者程序。 然后,我们可以使用这个位置来获取用户附近的餐馆列表。
Stream<({double longitude, double latitude})> location(Ref ref) {
// TO-DO: Return a stream which obtains the current location
return someStream;
}
Future<List<String>> restaurantsNearMe(Ref ref) async {
// We use "ref.watch" to obtain the latest location.
// By specifying that ".future" after the provider, our code will wait
// for at least one location to be available.
final location = await ref.watch(locationProvider.future);
// We can now make a network request based on that location.
// For example, we could use the 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>',
}),
);
// Obtain the restaurant names from the 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();
}
当监听的提供者程序发生更改并且我们的请求重新计算时,将保留以前的状态,直到新请求完成。
同时,当请求处于挂起状态时,将设置 "isLoading" 和 "isReloading" 标志。
这使 UI 能够显示以前的状态或加载指示器,甚至两者兼而有之。
请注意我们如何使用 ref.watch(locationProvider.future)
来替代 ref.watch(locationProvider)
。
那是因为我们 locationProvider
是异步的。因此,我们希望等待初始值可用。
如果我们省略了这一点 .future
,我们将收到一个 AsyncValue
,
它是 locationProvider
当前状态的快照。但是,如果还没有可用的位置,
我们将无能为力。
调用 ref.watch
“命令式”执行的内部代码被认为是不好的做法。
这意味着在提供者程序的构建阶段可能未执行的任何代码。
这包括通告程序上的“监听器”回调或方法:
int example(Ref ref) {
ref.watch(otherProvider); // Good!
ref.onDispose(() => ref.watch(otherProvider)); // Bad!
final someListenable = ValueNotifier(0);
someListenable.addListener(() {
ref.watch(otherProvider); // Bad!
});
return 0;
}
class MyNotifier extends _$MyNotifier {
int build() {
ref.watch(otherProvider); // Good!
ref.onDispose(() => ref.watch(otherProvider)); // Bad!
return 0;
}
void increment() {
ref.watch(otherProvider); // Bad!
}
}
ref.listen
/listenSelf
方法。
该 ref.listen
方法是 ref.watch
的替代方法。
它类似于传统的 "listen"/"addListener" 方法。
它接受一个提供者程序和一个回调,
并在提供者程序的内容发生更改时调用该回调。
通常建议重构代码,您可以使用 ref.watch
替代 ref.listen
,
因为后者由于其命令性质而更容易出错。
但是 ref.listen
可以有助于添加一些快速逻辑而不必进行重大重构。
我们可以重写 ref.watch
示例并使用 ref.listen
代替
int example(Ref ref) {
ref.listen(otherProvider, (previous, next) {
print('Changed from: $previous, next: $next');
});
return 0;
}
在提供者程序的构建阶段使用 ref.listen
是完全安全的。
如果以某种方式重新计算提供者程序,则以前的监听器将被删除。
或者,您可以根据需要使用 ref.listen
的返回值手动删除监听器。
ref.read
方法
最后一个可用选项是 ref.read
。
它类似于 ref.watch
返回提供者程序的当前状态。
但与 ref.watch
不同的是,它不监听提供者程序。
因此,ref.read
应该只被用在你不能使用 ref.watch
的地方,
比如通告程序的内部方法。
class MyNotifier extends _$MyNotifier {
int build() {
// Bad! Do not use "read" here as it is not reactive
ref.read(otherProvider);
return 0;
}
void increment() {
ref.read(otherProvider); // Using "read" here is fine
}
}
ref.read
在提供者程序上使用时要小心,因为它不监听提供者程序,
因此如果不监听提供者程序,则该提供者程序可能会决定处置其状态。