这是一篇关于使用 riverpod_generator 实现 Riverpod "Family"(传参)功能的系统化整理文章。


Riverpod Generator 实战:系统化掌握 Family(传参)模式

在 Riverpod 中,Family 是一个核心概念,它允许我们向 Provider 传递参数,从而创建基于参数的动态状态(例如:根据 userID 获取用户信息)。

在使用 riverpod_generator(代码生成器)时,Family 的使用方式变得更加直观和类似于普通函数调用,彻底解决了旧版本中“只能传递一个参数”的痛点。本文将系统地介绍如何在代码生成模式下使用 Family。

1. 核心概念:什么是 Family?

可以将 Family 想象成一个 Map(映射)

  • Key(键):你传递的参数(如 ID、搜索关键词)。
  • Value(值):该参数对应的 Provider 状态。

当你请求 userProvider(id: 1)userProvider(id: 2) 时,Riverpod 会分别创建并缓存两个独立的状态。


2. 基础用法:函数式 Provider (Functional)

对于只读的、无副作用的 Provider(通常对应 FutureProviderProvider),我们通过在函数中添加参数来实现 Family。

定义 Provider

只需在函数签名中添加所需的参数即可,生成器会自动处理 .family 的逻辑。

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'user_provider.g.dart';

// 定义一个接受 int 类型 userId 的 Provider
@riverpod
Future<User> fetchUser(FetchUserRef ref, int userId) async {
  // 模拟网络请求
  final json = await http.get('api/user/$userId');
  return User.fromJson(json);
}

在 UI 中使用

使用时,直接像调用函数一样传递参数:

@override
Widget build(BuildContext context, WidgetRef ref) {
  // 监听特定 userId 的状态
  final userAsync = ref.watch(fetchUserProvider(123));

  return userAsync.when(
    data: (user) => Text(user.name),
    loading: () => const CircularProgressIndicator(),
    error: (err, stack) => Text('Error: $err'),
  );
}

3. 进阶用法:类 Provider (Notifier / AsyncNotifier)

对于需要修改状态或包含业务逻辑的 Provider(通常对应 NotifierAsyncNotifier),参数是通过 build 方法 传递的。

定义 Notifier

build 方法中定义参数,生成器会自动将这些参数注入到生成的 Provider 中。

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'message_notifier.g.dart';

@riverpod
class MessageNotifier extends _$MessageNotifier {
  // build 方法接受参数 chatroomId
  @override
  Future<List<Message>> build(String chatroomId) async {
    return _fetchMessages(chatroomId);
  }

  // 类内部的方法可以直接通过 this.chatroomId 访问参数(由生成器生成)
  Future<void> sendMessage(String text) async {
    await http.post('api/chat/${this.chatroomId}', body: text);
    // 刷新状态
    ref.invalidateSelf();
  }
}

使用方式

// 获取特定聊天室的 Notifier
ref.read(messageNotifierProvider('room_001').notifier).sendMessage('Hello');

4. 解决痛点:传递多个参数

在旧版 Riverpod 中,Family 仅限传递一个位置参数,若需传递多个参数,不得不使用 Tuple 或自定义类。

使用 riverpod_generator,这一限制被彻底解除。 你可以使用任意数量的参数,包括位置参数、命名参数、可选参数和默认值。

示例:多参数与命名参数

@riverpod
Future<List<Product>> products(
  ProductsRef ref, {
  required String category,
  int page = 1,      // 支持默认值
  String? sortOrder, // 支持可选参数
}) async {
  return await http.get('api/products?cat=$category&page=$page&sort=$sortOrder');
}

调用更加优雅

ref.watch(productsProvider(category: 'electronics', page: 2));

5. 关键注意事项

为了确保 Riverpod 的缓存机制正常工作,传递给 Family 的参数必须遵循以下规则:

  1. 一致性 (Equality)
    • 参数对象必须正确重写 ==hashCode
    • 基本类型(int, String, List 等)通常没问题,但自定义类必须实现值相等性(推荐使用 freezedequatable 包)。
    • 如果两个对象的 == 返回 true,Riverpod 就会复用同一个状态,而不会重新创建。
  1. 不要直接传递 UI 对象
    • 避免将 BuildContextAnimationController 等 UI 相关对象作为参数传递,这会导致内存泄漏或不稳定的行为。
  1. AutoDispose 默认开启
    • 使用 riverpod_generator 生成的 Provider 默认都是 autoDispose 的。即当不再有监听者时,状态会自动销毁。
    • 如果需要保持状态,请设置 @Riverpod(keepAlive: true)

总结

使用 riverpod_generator 实现 Family 功能带来了质的飞跃:

特性 旧版手动定义 新版 Generator (@riverpod)
参数数量 仅限 1 个 无限,支持命名、可选参数
定义位置 .family 修饰符 函数参数 或 build() 方法参数
类型安全 需手动指定泛型 自动推导
代码量 较繁琐 简洁清晰

这种方式不仅减少了样板代码,还让 Provider 的定义更贴近 Dart 原生函数的写法,极大地提高了代码的可读性和维护性。