在 Riverpod 中,“消费者”是 UI 界面与 Provider 之间的桥梁。它允许 Widget 监听状态变化、读取数据以及触发副作用。


一、 核心概念:WidgetRef

无论使用哪种消费者 Widget,其核心都是通过 WidgetRef 对象与 Provider 进行交互。

  • 作用:它是 UI 与 Provider 容器之间的中介。
  • 对比:类似于 Flutter 的 BuildContext,但 WidgetRef 专门用于访问 Provider。

二、 三种主要的消费者实现方式

根据不同的使用场景(无状态、有状态、局部刷新),Riverpod 提供了三种接入方式。

1. ConsumerWidget(最常用)

它是 StatelessWidget 的替代方案,适用于大多数不需要内部生命周期管理的页面或组件。

  • 结构:继承 ConsumerWidget 而非 StatelessWidget
  • 用法build 方法会额外接收一个 WidgetRef 参数。
class MyHomeView extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 使用 ref 监听 provider
    final value = ref.watch(myProvider);
    return Text(value);
  }
}

2. ConsumerStatefulWidget

它是 StatefulWidget 的替代方案,适用于需要使用 initStatedispose 等生命周期方法的情况。

  • 结构:由 ConsumerStatefulWidgetConsumerState 组成。
  • 优势ref 对象是 ConsumerState 的属性,因此在类的任何地方(包括 initState)都可以直接访问 ref,无需像 ConsumerWidget 那样在 build 中传递。
class MyHomeView extends ConsumerStatefulWidget {
  @override
  _MyHomeViewState createState() => _MyHomeViewState();
}

class _MyHomeViewState extends ConsumerState<MyHomeView> {
  @override
  void initState() {
    super.initState();
    // 直接使用 ref
    ref.read(myProvider);
  }

  @override
  Widget build(BuildContext context) {
    final value = ref.watch(myProvider);
    return Text(value);
  }
}

3. Consumer Widget(局部组件)

这是一种更细粒度的包装器,可以在 Widget 树内部直接使用,类似于 Builder

  • 适用场景
    • 当你不想将整个 Widget 转换成 ConsumerWidget 时。
    • 性能优化:通过缩小作用域,仅让受影响的局部小组件重新构建,避免整个大型 Widget 树刷新。
Widget build(BuildContext context) {
  return Scaffold(
    body: Consumer(
      builder: (context, ref, child) {
        final value = ref.watch(myProvider);
        return Text(value);
      },
    ),
  );
}

三、 如何使用 ref 进行交互

获取 ref 对象后,你可以调用以下三个核心方法:

1. ref.watch(provider)

  • 功能:监听 Provider 并在其值变化时触发 Widget 重新构建
  • 场景:放在 build 方法内部,用于获取 UI 需要展示的数据。
  • 注意:不要在异步回调(如 onPressed)中使用。

2. ref.listen(provider, (previous, next) { ... })

  • 功能:监听 Provider 的变化,但不触发重绘,而是执行特定的副作用(Action)
  • 场景:用于弹出对话框(Dialog)、显示 SnackBar 或执行页面跳转。
  • 位置:通常放在 build 方法的第一行或 ConsumerState 的生命周期中。

3. ref.read(provider)

  • 功能一次性读取 Provider 的当前值,不建立持续的监听关系。
  • 场景:通常用于点击事件的回调中(如 onPressed),或在不希望 UI 响应式刷新的地方获取数据。
  • 警告:切勿在 build 方法中直接使用 ref.read 来渲染 UI,因为这会导致 UI 无法随状态同步更新。

四、 进阶技巧与最佳实践

1. 性能优化:使用 select

如果你只需要 Provider 返回对象中的某个特定字段,可以使用 select 来避免无关数据变化导致的无效重绘。

// 只有当 user 的 name 发生变化时,此 Widget 才会重绘
final name = ref.watch(userProvider.select((user) => user.name));

2. 传递 ref 到普通对象

如果你需要在某些非 Widget 类中访问 Provider,可以通过构造函数将 ref 传入(通常建议传入 Ref 类型而非 WidgetRef)。

3. 注意事项

  • 不要在 build 之外异步地调用 ref.watch:这会导致不可预测的行为。
  • 优先使用 watch 而非 read:除非是在事件回调中。

五、 总结建议

场景 推荐方式
标准页面/组件 ConsumerWidget
需要生命周期 (initState/dispose) ConsumerStatefulWidget
超大型 Widget 树的局部刷新 Consumer (Wrapper)
UI 渲染 ref.watch
导航、弹窗等操作 ref.listen
点击按钮触发逻辑 ref.read