Riverpod 深度解析:从响应式本质到最小可运行闭环
本文基于 Flutter 项目实战,从"为什么需要 Riverpod"出发,用最少代码验证其核心能力,并拆解响应式依赖图的底层机制。
一、为什么是 Riverpod?
在 Flutter 状态管理的选型中,我们常常在 setState、Provider、Bloc 之间纠结。它们各有痛点:
| 方案 | 痛点 |
|---|---|
setState |
粗粒度全量重建,无法跨组件共享 |
Provider |
依赖 BuildContext,非 Widget 层无法使用;ProviderNotFoundException 是运行时错误 |
Bloc |
事件驱动的状态机,简单异步场景也要定义 Event/State,样板代码多 |
Riverpod 的破局点在于:用声明式的 ref.watch 同时完成"依赖注入"和"响应式订阅",把 iOS 中需要 DI 框架 + 响应式框架 + 状态机三件套才能搞定的事,收敛到一套 Provider 体系。
二、核心概念:Provider 即依赖节点
Riverpod 的世界由 Provider 构成,常用 3 种:
| Provider 类型 | 用途 | 类比 |
|---|---|---|
Provider |
只读依赖注入 | Service Locator |
StateProvider |
简单可变状态 | CurrentValueSubject |
FutureProvider |
异步状态,自动管理 loading/error/data | Result<T, Error> + 生命周期 |
关键认知 :Provider 之间通过 ref.watch 建立依赖边,自动构成一张响应式依赖图。
三、最小可运行闭环
3.1 环境准备
pubspec.yaml 中添加依赖:
yaml
dependencies:
flutter_riverpod: ^2.5.1
main.dart 用 ProviderScope 包裹根节点(Riverpod 的运行时容器):
dart
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const ProviderScope(child: DemoApp()));
}
3.2 Provider 定义(providers.dart,36 行)
dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../avatar_demo/application/get_user_profile_use_case.dart';
import '../avatar_demo/domain/repositories/user_profile_repository.dart';
import '../avatar_demo/infrastructure/repositories/user_profile_repository_impl.dart';
// 1. 依赖注入:Repository(可 override 替换实现)
final userProfileRepositoryProvider = Provider<UserProfileRepository>((Ref ref) {
return UserProfileRepositoryImpl(remoteDataSource: MockUserProfileRemoteDataSource());
});
// 2. Provider 间依赖:UseCase 依赖 Repository
final getUserProfileUseCaseProvider = Provider<GetUserProfileUseCase>((Ref ref) {
return GetUserProfileUseCase(repository: ref.watch(userProfileRepositoryProvider));
});
// 3. 简单可变状态:计数器
final counterProvider = StateProvider<int>((Ref ref) => 0);
// 4. 异步状态:FutureProvider 自动管理三态
final userProfileFutureProvider = FutureProvider<UserProfile>((Ref ref) async {
return ref.watch(getUserProfileUseCaseProvider)();
});
3.3 消费页面(riverpod_demo_page.dart,核心片段)
dart
class RiverpodDemoPage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final int counter = ref.watch(counterProvider);
final AsyncValue<UserProfile> profileAsync = ref.watch(userProfileFutureProvider);
return Scaffold(
body: ListView(children: [
// StateProvider 读 + 写
Text('$counter'),
IconButton(onPressed: () => ref.read(counterProvider.notifier).state++),
// FutureProvider 三态
profileAsync.when(
loading: () => CircularProgressIndicator(),
error: (e, _) => Text('失败:$e'),
data: (p) => Text(p.name),
),
// 刷新
FilledButton(onPressed: () => ref.invalidate(userProfileFutureProvider)),
]),
);
}
}
四、深度拆解:ref.watch vs ref.read
这是 Riverpod 最容易混淆、也最核心的设计。
4.1 本质区别
| 操作 | 是否订阅 | 返回值 | 使用场景 |
|---|---|---|---|
ref.watch(provider) |
✅ 建立订阅,值变化时重建 | Provider 的值 | build 方法内 |
ref.read(provider) |
❌ 一次性取值 | Provider 的值 | 手势回调、定时器等非 build 场景 |
4.2 .notifier 修饰符
StateProvider 暴露两个层面:
csharp
counterProvider → StateProvider<int>
ref.watch(counterProvider) → int (值,只读)
ref.read(counterProvider) → int (值,只读)
counterProvider.notifier → Provider<StateController<int>>
ref.read(...notifier) → StateController<int> (控制器,可写)
写操作必须走 .notifier + ref.read:
dart
// ✅ 正确:read 不订阅,notifier 拿到控制器,.state 写入
ref.read(counterProvider.notifier).state++;
// ❌ 错误:watch 在回调中无意义,且返回的 int 没有 .state
ref.watch(counterProvider.notifier).state++;
4.3 完整数据流
五、Riverpod 相比其他方案的核心优势
5.1 编译期安全
Provider 是全局静态变量,写错名称编译直接报错,不会像 Provider.of(context) 那样运行时才抛 ProviderNotFoundException。
5.2 不依赖 BuildContext
ref 可以在 Repository、UseCase、定时器等非 Widget 层使用,解决了 Provider 包最大的限制。
5.3 自动生命周期管理
FutureProvider自动执行并管理 loading/error/dataautoDispose修饰符让 Provider 在无人监听时自动销毁AsyncValue.when强制处理三态,避免漏写 loading/error
5.4 声明式依赖图
dart
// 这一行同时完成了:依赖注入 + 响应式订阅
final bProvider = Provider((ref) => ref.watch(aProvider) * 2);
A 变化 → B 自动重算 → 监听 B 的 Widget 自动重建。无需手动 subscribe、disposeBag、combineLatest。
六、与 iOS 生态的类比
| Riverpod | iOS 对应物 |
|---|---|
Provider |
Service Locator + Combine Publisher |
ref.watch |
sink.receiveValue / KVO observe |
ref.read |
value(forKey:) |
AsyncValue |
Result<T, Error> + loading 态 |
ProviderScope |
DI Container(Swinject) |
override |
SwiftUI .environment 注入 |
最贴切的类比是 SwiftUI 的 @Observable + 依赖注入 ,但 Riverpod 把它泛化为了全局依赖图,且不需要手动 @StateObject 持有、不需要手动触发异步、不需要手动处理三态。
七、踩坑提示
ProviderRef已废弃 :Riverpod 2.5+ 统一为Ref,StateProviderRef、FutureProviderRef同理WidgetRef未废弃:它是 Widget 层专用接口,3.0 仍保留- 写操作用
ref.read:回调中用ref.watch不会建立有效订阅 ref.invalidatevsref.refresh:前者标记废弃下次读取时重建,后者立即重建并返回新 Future
八、总结
Riverpod 用一套 Provider 体系统一了:
- 依赖注入 (
Provider+override) - 响应式传播 (
ref.watch+ 依赖图) - 异步状态管理 (
FutureProvider+AsyncValue) - 生命周期 (
ProviderScope+autoDispose)
最小验证只需 2 个文件、约 160 行代码,即可覆盖上述全部能力。对于中大型 Flutter 工程,Riverpod 是兼顾类型安全、可测试性和开发效率的优选方案。
