在 Riverpod 中,代码生成(Code Generation)是一种通过注解(Annotation)自动生成 Provider 代码的技术。它不仅能简化语法,还能提供更强的类型安全和开发体验。
本文将系统介绍代码生成的优势、适用场景、核心语法以及如何从旧写法迁移。
1. 为什么使用代码生成?
虽然在 Riverpod 中代码生成是可选的,但它解决了手动定义 Provider 时的诸多痛点,并带来了显著的优势:
- 更佳的语法与可读性:
- 无需手动选择 Provider 类型(如
FutureProvider、StreamProvider等),只需编写正常的 Dart 函数或类,系统会自动根据返回值匹配最合适的类型。 - 告别“全局变量”式的定义,代码看起来更像是定义自定义函数或类。
- 无需手动选择 Provider 类型(如
- 无限制的参数传递(替代 Family):
- 不再受限于
.family只能传递单一位置参数的限制。 - 你可以直接传递任意数量的参数,包括命名参数、可选参数,甚至设置默认值。
- 不再受限于
- 开发体验提升:
- 支持 有状态的热重载(Stateful Hot-reload)。
- 生成额外的元数据,提供更详尽的调试信息。
- 降低学习曲线:你只需要关注业务逻辑(函数或类),无需记忆各种 Provider 的变体。
我应该使用代码生成吗?
官方建议如下:
- 推荐使用:如果你的项目中已经使用了代码生成工具(例如
Freezed或json_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) |
|---|---|
ProviderProvider.autoDispose((ref) => 'foo'); |
@riverpod 函数@riverpodString example(Ref ref) => 'foo'; |
FutureProviderFutureProvider.autoDispose((ref) async => 'foo'); |
@riverpod Future 函数@riverpodFuture<String> example(Ref ref) async => 'foo'; |
StreamProviderStreamProvider.autoDispose((ref) async* => 'foo'); |
@riverpod Stream 函数@riverpodStream<String> example(Ref ref) async* => 'foo'; |
NotifierProviderclass MyNotifier extends Notifier<String> { ... } |
@riverpod 类@riverpodclass Example extends _$Example { ... } |
AsyncNotifierProviderclass MyNotifier extends AsyncNotifier<String> { ... } |
@riverpod 类 (返回 Future)@riverpodclass Example extends _$Example {Future<String> build() async { ... }} |
5. 总结
使用 riverpod_generator 的核心逻辑是:你只管写业务函数,Riverpod 负责生成 Provider。
- 定义:使用
@riverpod注解。 - 类型:根据返回值(String, Future, Stream)自动推导。
- 参数:直接在函数参数中定义,无需 Family。
- 销毁:默认自动销毁,需长存则设
keepAlive: true。