[译][官方文档] Flutter/Dart 状态管理库 Riverpod (十)- 概要 - 绑定请求

原文链接: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 可能会在不被监听时决定清除它的状态。


相关推荐
Jewel1057 小时前
Flutter代码混淆
android·flutter·ios
一头小火烧17 小时前
flutter打包签名问题
flutter
sunly_17 小时前
Flutter:异步多线程结合
flutter
AiFlutter17 小时前
Flutter网络通信-封装Dio
flutter
B.-17 小时前
Flutter 应用在真机上调试的流程
android·flutter·ios·xcode·android-studio
有趣的杰克17 小时前
Flutter【04】高性能表单架构设计
android·flutter·dart
sunly_1 天前
Flutter:父组件,向子组件传值,子组件向二级页面传值
flutter
爱学习的绿叶1 天前
flutter TabBarView 动态添加删除页面
flutter
趴菜小玩家1 天前
使用 Gradle 插件优化 Flutter Android 插件开发中的 Flutter 依赖缺失问题
android·flutter·gradle
jhonjson2 天前
Flutter开发之flutter_local_notifications
flutter·macos·cocoa