Riverpod 深度解析:从响应式本质到最小可运行闭环

0 阅读4分钟

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/dataResult<T, Error> + 生命周期

关键认知:Provider 之间通过 ref.watch 建立依赖边,自动构成一张响应式依赖图。

graph LR
    A[userProfileRepositoryProvider<br/>Provider] -->|ref.watch| B[getUserProfileUseCaseProvider<br/>Provider]
    B -->|ref.watch| C[userProfileFutureProvider<br/>FutureProvider]
    C -->|ref.watch| D[RiverpodDemoPage<br/>ConsumerWidget]
    E[counterProvider<br/>StateProvider] -->|ref.watch| D

三、最小可运行闭环

3.1 环境准备

pubspec.yaml 中添加依赖:

dependencies:
  flutter_riverpod: ^2.5.1

main.dart 用 ProviderScope 包裹根节点(Riverpod 的运行时容器):

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const ProviderScope(child: DemoApp()));
}

3.2 Provider 定义(providers.dart,36 行)

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,核心片段)

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 暴露两个层面:

counterProvider           → StateProvider<int>
  ref.watch(counterProvider) → int                 (值,只读)
  ref.read(counterProvider)  → int                 (值,只读)

counterProvider.notifier  → Provider<StateController<int>>
  ref.read(...notifier)     → StateController<int> (控制器,可写)

写操作必须走 .notifier + ref.read:

// ✅ 正确:read 不订阅,notifier 拿到控制器,.state 写入
ref.read(counterProvider.notifier).state++;

// ❌ 错误:watch 在回调中无意义,且返回的 int 没有 .state
ref.watch(counterProvider.notifier).state++;

4.3 完整数据流

sequenceDiagram
    participant U as 用户
    participant B as IconButton
    participant SC as StateController
    participant P as counterProvider
    participant W as ConsumerWidget

    U->>B: 点击 + 按钮
    B->>SC: ref.read(counterProvider.notifier).state++
    SC->>SC: state setter 比较新旧值
    SC-->>P: 广播 didUpdateProvider
    P-->>W: 通知所有 ref.watch 的 Element
    W->>W: build 重建
    W->>P: ref.watch 重新执行 → 拿到新值
    W-->>U: 渲染新数字

五、Riverpod 相比其他方案的核心优势

5.1 编译期安全

Provider 是全局静态变量,写错名称编译直接报错,不会像 Provider.of(context) 那样运行时才抛 ProviderNotFoundException。

5.2 不依赖 BuildContext

ref 可以在 Repository、UseCase、定时器等非 Widget 层使用,解决了 Provider 包最大的限制。

5.3 自动生命周期管理

  • FutureProvider 自动执行并管理 loading/error/data
  • autoDispose 修饰符让 Provider 在无人监听时自动销毁
  • AsyncValue.when 强制处理三态,避免漏写 loading/error

5.4 声明式依赖图

// 这一行同时完成了:依赖注入 + 响应式订阅
final bProvider = Provider((ref) => ref.watch(aProvider) * 2);

A 变化 → B 自动重算 → 监听 B 的 Widget 自动重建。无需手动 subscribe、disposeBag、combineLatest。


六、与 iOS 生态的类比

RiverpodiOS 对应物
ProviderService Locator + Combine Publisher
ref.watchsink.receiveValue / KVO observe
ref.readvalue(forKey:)
AsyncValueResult<T, Error> + loading 态
ProviderScopeDI Container(Swinject)
overrideSwiftUI .environment 注入

最贴切的类比是 SwiftUI 的 @Observable + 依赖注入,但 Riverpod 把它泛化为了全局依赖图,且不需要手动 @StateObject 持有、不需要手动触发异步、不需要手动处理三态。


七、踩坑提示

  1. ProviderRef 已废弃:Riverpod 2.5+ 统一为 Ref,StateProviderRef、FutureProviderRef 同理
  2. WidgetRef 未废弃:它是 Widget 层专用接口,3.0 仍保留
  3. 写操作用 ref.read:回调中用 ref.watch 不会建立有效订阅
  4. ref.invalidate vs ref.refresh:前者标记废弃下次读取时重建,后者立即重建并返回新 Future

八、总结

Riverpod 用一套 Provider 体系统一了:

  • 依赖注入(Provider + override)
  • 响应式传播(ref.watch + 依赖图)
  • 异步状态管理(FutureProvider + AsyncValue)
  • 生命周期(ProviderScope + autoDispose)

最小验证只需 2 个文件、约 160 行代码,即可覆盖上述全部能力。对于中大型 Flutter 工程,Riverpod 是兼顾类型安全、可测试性和开发效率的优选方案。

image.png