1. 什么是 Ref?

在 Riverpod 中,Ref 是你与各个 Provider 进行交互的主要接口。 如果将 Flutter Widget 树中的 BuildContext 比作组件与框架交互的桥梁,那么 Ref 就是 Provider 之间、或 Widget 与 Provider 之间交互的桥梁。

Ref 的主要作用:

  • 读取(Reading):获取 Provider 当前的数据。
  • 监听(Watching):订阅 Provider 的变化,当数据改变时触发重建。
  • 副作用(Listening):监听 Provider 的变化以执行某些操作(如弹窗、导航),而不触发重建。
  • 生命周期管理:控制 Provider 的销毁与缓存(例如 ref.keepAliveref.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)RefNotifier 类的内置属性,可以直接通过 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 接受两个参数:

  1. 目标 Provider。
  2. 一个回调函数 (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 的当前值,监听变化。 场景用于由用户交互触发的回调函数中(如 onPressedonTap)。

重要原则

  • 绝对不要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. 决策指南:我该用哪一个?

为了简化决策过程,请遵循以下决策流:

场景 推荐方法 理由
**我想