likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 4

!!!译文为作者本人人肉翻译~转载请注明出处!!!


原文链接:Combining requests | Riverpod

pub:riverpod | Dart Package (flutter-io.cn)

译时版本: 2.4.9



绑定请求

迄今为止,只看到了相互独立请求的场景。但是一个常见的使用场景是需要基于另一个请求的结果触发某个请求。

但是该方式有一些缺点:

  • 这会泄漏实现细节。现在,UI 需要知道所有使用了其它 provider 的 provider 。
  • 无论只要参数发生改变,就会创建一个全新的状态。对于传递参数这种方式,当参数改变时,没有保持前一次状态的方法。
  • 绑定请求更加困难。
  • 相关工具不再适用。devtool 不会知道 provider 之间的关系。

要改善该问题,Riverpod 提供了不同的方式绑定请求。

基础用法:获取 "ref"

绑定请求的所有可能方式有一个共通点:它们都基于 Ref 对象。

Ref 对象是所有 provider 都能访问的对象。 它赋予它们对多种生命周期监听器的访问权限,也有多种绑定 provider 的方法。

在哪里能获取到 Ref 取决于 provider 的类型。

在函数式的 provider 里,Ref 是作为参数传递给 provider 的函数:

@riverpod
int example(ExampleRef ref) {
  // "Ref" 在这里可用来读取其它 provider
  final otherValue = ref.watch(otherProvider);

  return 0;
}

在类变量中,Ref 是 Notifier 类的一个属性:

@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 监听用户的位置。然后使用该位置获取用户附近的餐厅列表。

@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 的方法:

@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示例

@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 的方法内部。

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


转载自:https://juejin.cn/post/7307472360533803044
评论
请登录