1. 什么是 Ref?
在 Riverpod 中,Ref 是你与各个 Provider 进行交互的主要接口。 如果将 Flutter Widget 树中的 BuildContext 比作组件与框架交互的桥梁,那么 Ref 就是 Provider 之间、或 Widget 与 Provider 之间交互的桥梁。
Ref 的主要作用:
- 读取(Reading):获取 Provider 当前的数据。
- 监听(Watching):订阅 Provider 的变化,当数据改变时触发重建。
- 副作用(Listening):监听 Provider 的变化以执行某些操作(如弹窗、导航),而不触发重建。
- 生命周期管理:控制 Provider 的销毁与缓存(例如
ref.keepAlive或ref.invalidate)。
2. 如何获取 Ref
根据你所在的代码位置不同,获取 Ref 的方式也有所不同。
2.1 在 Provider 内部(使用 riverpod_generator)
当你使用 @riverpod 定义 Provider 时,Ref 会自动作为参数传递给你的构建函数,或者作为属性存在于类中。
- 函数式 Provider (Functional Provider):
Ref对象(或其具体子类型,如HelloWorldRef)作为第一个参数传入。
```dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'my_provider.g.dart';
@riverpod
String helloWorld(Ref ref) {
// 在这里可以使用 ref.watch 或 ref.read
return 'Hello world';
}
```
- 类式 Provider (Notifier Provider):
Ref是Notifier类的内置属性,可以直接通过ref访问。
```dart
@riverpod
class Counter extends _$Counter {
@override
int build() {
// 在 build 方法中可以使用 ref
// 例如:ref.watch(anotherProvider);
return 0;
}
void increment() {
// 在方法中也可以使用 ref
// 例如:ref.read(authProvider);
state++;
}
}
```
2.2 在 Widget 内部
在 Flutter UI 中,我们需要使用 WidgetRef。它与 Provider 内部的 Ref API 非常相似,但专门用于 Widget。通常通过继承 ConsumerWidget 来获取。
class Home extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// ref 参数由 ConsumerWidget 提供
final count = ref.watch(counterProvider);
return Text('$count');
}
}
3. Ref 的核心用法:交互的三种模式
Ref 提供了三种主要方法来与 Provider 交互,理解它们的区别至关重要。
3.1 ref.watch —— 响应式监听(推荐)
用途:获取 Provider 的值,并监听其变化。 场景:通常用于 Provider 的 build 方法中,或 Widget 的 build 方法中。
当被监听的 Provider 更新时,ref.watch 会导致调用它的 Widget 或 Provider 重新构建。这是 Riverpod 构建响应式 UI 的核心。
示例:组合 Provider
@riverpod
String name(Ref ref) => 'Riverpod';
@riverpod
String greeting(Ref ref) {
// 监听 nameProvider。
// 一旦 nameProvider 的值发生变化,greetingProvider 也会自动重新计算。
final name = ref.watch(nameProvider);
return 'Hello $name';
}
3.2 ref.listen —— 副作用监听
用途:监听 Provider 的变化,执行自定义逻辑(如显示 SnackBar、对话框、页面跳转),但不触发重建。 场景:通常用于 Widget 的 build 方法中(作为设置监听器的位置)。
ref.listen 接受两个参数:
- 目标 Provider。
- 一个回调函数
(previous, next),分别代表旧值和新值。
示例:显示 SnackBar
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 监听 counterProvider
ref.listen(counterProvider, (previous, next) {
if (next > 5) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Count is high!')),
);
}
});
return Scaffold(/* ... */);
}
}
3.3 ref.read —— 一次性读取(谨慎使用)
用途:获取 Provider 的当前值,不监听变化。 场景:仅用于由用户交互触发的回调函数中(如 onPressed、onTap)。
重要原则:
- ❌ 绝对不要在
build方法中使用ref.read。这会破坏响应式机制。 - ✅ 推荐在按钮点击事件等回调中使用。
示例:在按钮点击中读取
class MyButton extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return ElevatedButton(
onPressed: () {
// ✅ 正确用法:在回调中使用 ref.read
// 我们不需要监听变化,只需要在点击的一瞬间获取 Counter 的类实例来调用方法
ref.read(counterProvider.notifier).increment();
},
child: Text('Increment'),
);
}
}
4. 决策指南:我该用哪一个?
为了简化决策过程,请遵循以下决策流:
| 场景 | 推荐方法 | 理由 |
|---|---|---|
| **我想 |