在 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 的替代方案,适用于需要使用 initState、dispose 等生命周期方法的情况。
- 结构:由
ConsumerStatefulWidget和ConsumerState组成。 - 优势:
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 转换成
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 |