組合請求
到目前為止,我們只看到請求彼此獨立的案例。 但一個常見的用例是必須根據另一個請求的結果觸發請求。
我們可以使用將引數傳遞給您的請求機制來做到這一點, 方法是將一個提供者程式的結果作為引數傳遞給另一個提供者程式。
但這種方法有一些缺點:
- 這洩露了實現細節。 現在,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
在提供者程式上使用時要小心,因為它不監聽提供者程式,
因此如果不監聽提供者程式,則該提供者程式可能會決定處置其狀態。