[官文翻译]Flutter状态管理库Riverpod - 概念 - 修饰符 .family

757 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情


Riverpod的官方文档有多国语言,但是没有汉语,所以个人简单翻译了一版。

官网文档:Riverpod

GitHub:GitHub - rrousselGit/river_pod

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

译时版本:riverpod 1.0.3


.family

阅读该指南之前,确保首先阅读关于 Provider 的内容 和 如何读取它们

在这部分,我们会详细说一下关于 provoide 的 .family 修饰符的内容。

.family 修饰符有一个目的:获取基于外部参数的唯一 provider 。

使用 family 的一些场景可能会是:

  • 用 .family 绑定 FutureProvider ,用它的 ID 获取 Message
  • 传递当前的 Locale 给 provider,这样可以处理本地化(多国语言)。

用法

family 的运行机制是通过为 provider 添加额外的参数。然后该参数可以在 provider 中自由使用以创建一些状态。

例如,可以用 FutureProvider 绑定 family ,用它的 ID 获取 Message

final messagesFamily = FutureProvider.family<Message, String>((ref, id) async {
  return dio.get('http://my_api.dev/messages/$id');
});

使用 messagesFamily provider 时,语法会有轻微的区别。 通常的语法无法继续使用:

Widget build(BuildContext context, WidgetRef ref) {
  // Error – messagesFamily 不是 provider
  final response = ref.watch(messagesFamily);
}

取而代之的是,需要传递参数给 messagesFamily

Widget build(BuildContext context, WidgetRef ref) {
  final response = ref.watch(messagesFamily('id'));
}

信息

使用 family 可以同时带不同的参数。 例如,可以使用 titleFamily 同时读取法语和英语的翻译:

@override
Widget build(BuildContext context, WidgetRef ref) {
  final frenchTitle = ref.watch(titleFamily(const Locale('fr')));
  final englishTitle = ref.watch(titleFamily(const Locale('en')));

  return Text('fr: $frenchTitle en: $englishTitle');
}

参数限制

为了让 family 正确运行,对于参数来说,传递一个带有永远一致的 hashCode 和 == 的 provider 很重要。

理想的情况是,参数应该是基本类型(bool/整数/浮点数/字符串)、常数(provider),或覆写了 == 和 hashCode 的不可变对象。

推荐 当参数会发生变化时使用 autoDispose 

你可能想使用 family 传递一个查找相关的输入给 provider。 但是那个值经常改变,而且不会再被使用。 这会造成内存泄露,因为默认情况下 provider 永远不会被释放,即使不再使用。

You may want to use families to pass the input of a search field to your provider. But that value can change often and never be reused.
This could cause memory leaks as, by default, a provider is never destroyed even if no longer used.

同时使用 .family 和 .autoDispose 可以修复这种内存泄露::

final characters = FutureProvider.autoDispose.family<List<Character>, String>((ref, filter) async {
  return fetchCharacters(filter: filter);
});

传递多个参数给 family

family 没有内置给 provider 传递多个值的支持。

另一方面,这个值可以是 任意的 (只要它满足前面提到的限制)。

这包括:

这里有一个使用 Freezed 或 equatable 传递多个参数的示例:

  • Freezed

    @freezed
    abstract class MyParameter with _$MyParameter {
      factory MyParameter({
        required int userId,
        required Locale locale,
      }) = _MyParameter;
    }
    
    final exampleProvider = Provider.autoDispose.family<Something, MyParameter>((ref, myParameter) {
      print(myParameter.userId);
      print(myParameter.locale);
      // 使用用户ID或 locale 做一些处理
    });
    
    @override
    Widget build(BuildContext context, WidgetRef ref) {
      int userId; // 从其他什么地方读取用户 ID
      final locale = Localizations.localeOf(context);
    
      final something = ref.watch(
        exampleProvider(MyParameter(userId: userId, locale: locale)),
      );
    
      ...
    }
    
  • Equatable

class MyParameter extends Equatable  {
  MyParameter({
    required this.userId,
    required this.locale,
  });

  final int userId;
  final Locale locale;

  @override
  List<Object> get props => [userId, locale];
}

final exampleProvider = Provider.family<Something, MyParameter>((ref, myParameter) {
  print(myParameter.userId);
  print(myParameter.locale);
  // 使用用户ID或 locale 做一些处理
});

@override
Widget build(BuildContext context, WidgetRef ref) {
  int userId; // 从其他什么地方读取用户 ID
  final locale = Localizations.localeOf(context);

  final something = ref.watch(
    exampleProvider(MyParameter(userId: userId, locale: locale)),
  );

  ...
}