速查 06 · Riverpod
按问题索引。Ctrl+F 页内搜索。 系统学习见 08 · 状态管理 Riverpod, 选型见 27 · 状态管理选型与横向对比。
本项目版本:Riverpod 2.6。
目录
- provider 怎么选
- 定义一个 provider
- watch / read / listen 怎么选
- AsyncValue 三态处理
- 传参:family
- 自动销毁:autoDispose
- 局部刷新:select
- 刷新与失效
- 在 Widget 中使用
- 测试与覆盖
- 常见报错速查
provider 怎么选
| 我要… | 用什么 |
|---|---|
| 只读的计算值(不会变) | Provider |
| 简单的同步状态(计数器、开关) | NotifierProvider + Notifier<T> |
| 异步请求(拉接口、读库) | AsyncNotifierProvider + AsyncNotifier<T> |
| 一次性 Future(无交互) | FutureProvider |
| 流(WebSocket、监听) | StreamProvider |
| 需要参数(按 id 查详情) | 任意 provider + .family |
| 离开页面就销毁 | 任意 provider + .autoDispose |
⚠️
StateProvider/StateNotifierProvider/ChangeNotifierProvider是旧 API, 新代码优先用NotifierProvider/AsyncNotifierProvider。
定义一个 provider
手写写法(Riverpod 2.x 推荐)
dart
// 同步状态
class CounterNotifier extends Notifier<int> {
@override
int build() => 0; // build 里是初始值,**不要在这里做副作用**
void increment() => state++;
}
final counterProvider =
NotifierProvider<CounterNotifier, int>(CounterNotifier.new);
// 异步状态
class UserNotifier extends AsyncNotifier<User> {
@override
Future<User> build() => ref.read(userRepoProvider).fetch();
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(() => ref.read(userRepoProvider).fetch());
}
}
final userProvider =
AsyncNotifierProvider<UserNotifier, User>(UserNotifier.new);代码生成写法(riverpod_generator)
dart
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
}
// 跑 build_runner 后得到 counterProvider💡 两种写法可以混用。新项目推荐 codegen(少写泛型),老项目改造用手写更省事。
watch / read / listen 怎么选
| 用法 | 行为 | 用在哪 |
|---|---|---|
ref.watch(p) | 值变了重建 Widget | build 方法里 |
ref.read(p) | 只取一次,不订阅 | 回调、onPressed、initState |
ref.listen(p, (prev, next) {}) | 变化时不重建,只触发副作用(弹窗、跳转) | build 里注册 |
dart
// ❌ 别在回调里用 watch(会订阅但永远不重建,且可能报错)
onPressed: () => ref.watch(counterProvider).increment()
// ✅ 回调里用 read
onPressed: () => ref.read(counterProvider.notifier).increment()
// ✅ 监听错误弹提示
ref.listen(userProvider, (prev, next) {
if (next.hasError) showSnackBar(next.error.toString());
});AsyncValue 三态处理
dart
final userAsync = ref.watch(userProvider);
// ① 全量处理(最常用)
userAsync.when(
data: (user) => Text(user.name),
loading: () => const CircularProgressIndicator(),
error: (e, st) => Text('出错了:$e'),
);
// ② 只处理部分
userAsync.maybeWhen(
data: (user) => Text(user.name),
orElse: () => const SizedBox.shrink(),
);
// ③ 拿原始值(可能为 null,自己判空)
final user = userAsync.value; // data 时才非 null
if (userAsync.isLoading) { /* ... */ }
if (userAsync.hasError) { /* ... */ }💡 想"刷新时保留旧数据":
state = const AsyncLoading()之前先存旧值, 或用AsyncValue.guard+valueOrNull展示旧数据 + loading 遮罩。
传参:family
dart
final userDetailProvider =
AsyncNotifierProviderFamily<UserDetailNotifier, User, String>(
UserDetailNotifier.new,
);
// 使用
ref.watch(userDetailProvider(userId));
// codegen 写法:参数直接写在 build 上
@riverpod
Future<User> userDetail(UserDetailRef ref, String id) => ...;⚠️
family的参数必须可被==比较(别传List/Map/未重写的对象,否则每次都重建缓存)。
自动销毁:autoDispose
dart
final searchProvider =
NotifierProvider.autoDispose<SearchNotifier, List<Item>>(SearchNotifier.new);
// 想"没人听也再活一会儿"
final p = SomeProvider(...);
ref.keepAlive(); // 在 build 里调用,阻止自动销毁用途:搜索页、详情页这类离开即丢的状态,避免内存堆积。
局部刷新:select
dart
// ❌ 整个 user 变了就重建,哪怕只用 name
final user = ref.watch(userProvider).value;
// ✅ 只有 name 变了才重建(Riverpod 2.x 推荐)
final name = ref.watch(userProvider.select((s) => s.valueOrNull?.name));🎯 这是性能优化第一手段:大对象只取用到的字段。
刷新与失效
dart
ref.invalidate(userProvider); // 标记失效,下次读时重新 build
ref.refresh(userProvider); // 立即重建并返回新值
ref.invalidateSelf(); // 在 Notifier 内部让自己失效在 Widget 中使用
dart
// ① 整个 Widget 需要 provider → ConsumerWidget
class UserPage extends ConsumerWidget {
const UserPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(userProvider);
return ...;
}
}
// ② 需要生命周期(initState / dispose)→ ConsumerStatefulWidget
class _UserPageState extends ConsumerState<UserPage> { ... }
// ③ 只有一小块需要 → Consumer 包局部(重建范围最小)
Consumer(builder: (context, ref, child) => Text(ref.watch(nameProvider)))✅ 优先用 ③
Consumer:重建范围越小,性能越好。
测试与覆盖
dart
testWidgets('显示用户名', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
userProvider.overrideWith(FakeUserNotifier.new), // 换成假实现
],
child: const MaterialApp(home: UserPage()),
),
);
expect(find.text('张三'), findsOneWidget);
});⚠️ 忘了
ProviderScope是最常见的新手错误(见下方报错表)。
常见报错速查
| 报错 / 现象 | 原因 | 解法 |
|---|---|---|
Bad state: No ProviderScope found | 根 Widget 没包 ProviderScope | runApp(const ProviderScope(child: MyApp())) |
在 initState 里 ref.watch 报错或无效 | initState 不能订阅 | 改用 ref.read;或放到 build 里 watch |
Cannot use "ref" after the widget was disposed | 异步 gap 后用了 ref | await 后先判 if (!context.mounted) return;,或 if (!ref.context.mounted) |
Provider was disposed / 取到旧值 | 用了 autoDispose 且已无监听 | 需要保留就去掉 .autoDispose 或 ref.keepAlive() |
family 参数每次都重新请求 | 传了 List/Map 或未重写 == | 传基础类型或重写 ==(或配合 Freezed) |
| 改了值但 UI 不刷新 | 用了 ref.read 而非 watch;或没改 state 而是改了内部可变对象 | 用 state = 新对象,别在原地 mutate |
AsyncValue 一直 loading | build() 里的 Future 没返回 / 抛异常被吞 | 确认 build 有 return,异常用 AsyncValue.guard |
相关文档
- 08 · 状态管理 Riverpod —— 本项目真实写法
- 27 · 状态管理选型与横向对比 —— 为什么选它
- A07 · 状态管理演进与选型 —— 前端视角(Redux→Zustand→Signals)
- 速查 08 · Freezed —— 状态模型怎么定义