在 Riverpod 中,代码生成(Code Generation)是一种通过注解(Annotation)自动生成 Provider 代码的技术。它不仅能简化语法,还能提供更强的类型安全和开发体验。

本文将系统介绍代码生成的优势、适用场景、核心语法以及如何从旧写法迁移。

1. 为什么使用代码生成?

虽然在 Riverpod 中代码生成是可选的,但它解决了手动定义 Provider 时的诸多痛点,并带来了显著的优势:

  • 更佳的语法与可读性
    • 无需手动选择 Provider 类型(如 FutureProviderStreamProvider 等),只需编写正常的 Dart 函数或类,系统会自动根据返回值匹配最合适的类型。
    • 告别“全局变量”式的定义,代码看起来更像是定义自定义函数或类。
  • 无限制的参数传递(替代 Family)
    • 不再受限于 .family 只能传递单一位置参数的限制。
    • 你可以直接传递任意数量的参数,包括命名参数、可选参数,甚至设置默认值。
  • 开发体验提升
    • 支持 有状态的热重载(Stateful Hot-reload)
    • 生成额外的元数据,提供更详尽的调试信息。
  • 降低学习曲线:你只需要关注业务逻辑(函数或类),无需记忆各种 Provider 的变体。

我应该使用代码生成吗?

官方建议如下:

  • 推荐使用:如果你的项目中已经使用了代码生成工具(例如 Freezedjson_serializable),那么引入 Riverpod 代码生成非常简单且值得。
  • 谨慎考虑:如果你没有使用任何代码生成工具,专门为 Riverpod 配置环境可能会带来额外的编译耗时(因为 Dart 的 Macros 功能已被取消,目前代码生成速度仍有提升空间)。在这种情况下,你可以权衡是否值得为了上述优势而增加构建步骤。

2. 核心语法

使用 riverpod_generator 主要有两种方式定义 Provider:函数式(Functional)类式(Class-Based)

  • 函数式:适用于无副作用、仅通过 ref.watch 获取数据的简单逻辑(类似只读的 Provider)。
  • 类式:适用于需要定义公开方法来修改状态的复杂逻辑(即 Notifier)。

2.1 基础定义对比

同步 Provider (Sync)

类型 说明 示例代码
函数式 简单的值返回,不可外部修改 dart<br>@riverpod<br>String example(Ref ref) {<br> return 'foo';<br>}<br>
类式 可包含修改状态的方法 dart<br>@riverpod<br>class Example extends _$Example {<br> @override<br> String build() {<br> return 'foo';<br> }<br> // 可添加方法修改 state<br> void update() => state = 'bar';<br>}<br>

异步 Future Provider (Async)

当函数标记为 async 并返回 Future 时,生成的 Provider 会自动处理加载和错误状态,对外暴露 AsyncValue

类型 说明 示例代码
函数式 替代 FutureProvider dart<br>@riverpod<br>Future<String> example(Ref ref) async {<br> return Future.value('foo');<br>}<br>
类式 替代 AsyncNotifierProvider dart<br>@riverpod<br>class Example extends _$Example {<br> @override<br> Future<String> build() async {<br> return Future.value('foo');<br> }<br>}<br>

流 Stream Provider

当函数标记为 async* 并返回 Stream 时,同样对外暴露 AsyncValue

类型 说明 示例代码
函数式 替代 StreamProvider dart<br>@riverpod<br>Stream<String> example(Ref ref) async* {<br> yield 'foo';<br>}<br>
类式 替代 StreamNotifierProvider dart<br>@riverpod<br>class Example extends _$Example {<br> @override<br> Stream<String> build() async* {<br> yield 'foo';<br> }<br>}<br>

3. 进阶特性

3.1 自动销毁 (AutoDispose) 与 保持存活 (KeepAlive)

在代码生成模式下,所有 Provider 默认都是 autoDispose。这意味着当没有监听者时,状态会自动销毁。这与 Riverpod 的现代设计哲学一致。

如果你需要 Provider 在无监听时依然保持状态(KeepAlive),需在注解中显式配置:

// 默认:自动销毁 (AutoDispose)
@riverpod
String example1(Ref ref) => 'foo';

// 禁用自动销毁 (KeepAlive)
@Riverpod(keepAlive: true)
String example2(Ref ref) => 'foo';

3.2 传递参数 (替代 .family)

你不再需要复杂的 .family 修饰符。只需在函数或 build 方法中添加参数即可。

函数式示例:

@riverpod
String example(
  Ref ref, 
  int id, {
  String mode = 'default', // 支持命名参数和默认值
}) {
  return 'ID: $id, Mode: $mode';
}

类式示例:

@riverpod
class Example extends _$Example {
  @override
  String build(int id, {String mode = 'default'}) {
    return 'ID: $id, Mode: $mode';
  }
}

注意:传递的参数对象必须具有一致的 == (equality) 实现(例如使用 primitive types 或 equatable 对象),以便 Riverpod 正确缓存 Provider。


4. 迁移对照表 (Cheatsheet)

如果你熟悉旧的手动定义方式,可以参考下表进行迁移:

旧写法 (Manual) 新写法 (Code Generation)
Provider
Provider.autoDispose((ref) => 'foo');
@riverpod 函数
@riverpod
String example(Ref ref) => 'foo';
FutureProvider
FutureProvider.autoDispose((ref) async => 'foo');
@riverpod Future 函数
@riverpod
Future<String> example(Ref ref) async => 'foo';
StreamProvider
StreamProvider.autoDispose((ref) async* => 'foo');
@riverpod Stream 函数
@riverpod
Stream<String> example(Ref ref) async* => 'foo';
NotifierProvider
class MyNotifier extends Notifier<String> { ... }
@riverpod 类
@riverpod
class Example extends _$Example { ... }
AsyncNotifierProvider
class MyNotifier extends AsyncNotifier<String> { ... }
@riverpod 类 (返回 Future)
@riverpod
class Example extends _$Example {
Future<String> build() async { ... }
}

5. 总结

使用 riverpod_generator 的核心逻辑是:你只管写业务函数,Riverpod 负责生成 Provider

  • 定义:使用 @riverpod 注解。
  • 类型:根据返回值(String, Future, Stream)自动推导。
  • 参数:直接在函数参数中定义,无需 Family。
  • 销毁:默认自动销毁,需长存则设 keepAlive: true