Riverpod 完全指南:从入门到精通

Riverpod 完全指南:从入门到精通

一篇让你看完就能上手 Riverpod 的文章

目录

  1. Riverpod 是什么
  2. 为什么选择 Riverpod
  3. 与其他方案的对比
  4. 核心概念
  5. Provider 类型详解
  6. ref 的三种用法
  7. 高级特性
  8. 实战案例
  9. 最佳实践
  10. 总结

一、Riverpod 是什么

Riverpod 是由 Remi Rousselet 开发的响应式缓存与数据绑定框架,是 Provider 的继任者。它的名字是 Provider 的“变位词”(anagram),定位是一个 “响应式缓存和数据绑定框架”

简单来说,Riverpod 的核心工作方式是:Provider 是“带缓存的函数”,通过 ref 组合依赖、自动失效;UI 通过 ref.watch 订阅,状态变化时自动重建

截至 2026 年,Riverpod 已成为官方推荐、社区首选的状态管理方案,GitHub Stars 超 28k,被阿里、字节、腾讯等大厂广泛采用。

二、为什么选择 Riverpod

2.1 核心优势

特性 说明
编译期安全 不依赖 BuildContext,可在 initState、测试、纯 Dart 中使用
响应式缓存 Provider 即“带缓存的函数”,自动管理生命周期
依赖自动追踪 ref.watch 建立依赖图,依赖变化自动失效并重建
异步原生支持 FutureProvider、AsyncNotifier、AsyncValue 内置
可测试性 ProviderScope + overrides 轻松 mock
自动释放 autoDispose 无监听时自动 dispose
参数化 family 支持带参数的 Provider

2.2 解决了什么痛点

在 Flutter 项目中,状态管理常见痛点包括:

  • 页面状态散落在 StatefulWidget 中,后期难维护
  • 网络请求、loading、error、empty 状态处理重复
  • 多页面共享状态困难
  • Controller、Repository、Service 依赖关系混乱
  • 单元测试时很难 mock 数据源
  • 页面销毁后状态释放不清晰

Riverpod 通过不依赖 BuildContext、天然支持分层架构、AsyncValue 统一管理 loading/error/data、支持 overrides 测试、autoDispose 自动释放等机制,一一解决了这些问题。

三、与其他方案的对比

3.1 横向对比

维度 Provider Riverpod Bloc GetX
学习曲线 低-中
可测试性 良好 优秀 优秀 一般
样板代码
编译时安全 一般 优秀 优秀 一般
社区规模
异步类型安全 一般 优秀(AsyncValue) 良好 一般

3.2 各方案特点

Provider:Flutter 官方推荐的轻量级方案,底层基于 InheritedWidget,学习成本低。但复杂项目下 Provider 嵌套容易形成“金字塔地狱”,且依赖 BuildContext。

Bloc:企业级的事件驱动架构,强制单向数据流,适合大型团队。但样板代码多,一个小功能就要写 Event + State + Bloc 三个文件。

GetX:瑞士军刀式全能选手,API 极简,内置路由和依赖注入。但封装过深、存在隐式全局依赖,难以测试。

Riverpod:编译时安全、无需 Context、组合能力强、测试友好,代表 Flutter 状态管理的未来方向。

四、核心概念

4.1 四大核心组件

Riverpod 的核心由四个概念组成:

  1. Provider:状态的描述,本质是“带缓存的函数”,顶层声明,不可变
  2. ProviderContainer:存储 Provider 实际状态,Flutter 中由 ProviderScope 创建
  3. Ref:Provider 之间、Provider 与 UI 之间的桥梁
  4. Consumer:ConsumerWidget、Consumer、ConsumerStatefulWidget 等,提供 WidgetRef

4.2 ProviderScope

使用 Riverpod 的第一步,是在 Flutter 应用根节点包裹 ProviderScope

void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

ProviderScope 可以理解为 Riverpod 的状态容器,所有 Provider 的状态都由它管理。

4.3 工作原理简述

Riverpod 的核心机制可以概括为:

  • Provider 是纯函数描述:Provider 本身不可变,只是“如何计算值”的描述,不持有状态
  • ProviderContainer 存储状态:实际的状态存储在容器中
  • ref 建立连接:通过 ref 在 Provider 之间、Provider 与 UI 之间建立连接

当一个 widget 或 provider 请求一个 provider 的值时,Riverpod 在最近的 ProviderScope widget 中查找该 provider 的状态。

4.4 Provider 生命周期

所有 provider 都会经历相同的状态周期:

  • 未初始化/已销毁:不占用任何内存
  • 活动中:状态可被读取和监听,依赖关系建立
  • 暂停:当不再被监听时进入暂停状态,减少计算负担

当读取、监听或观察未初始化的 provider 时,会创建其状态并进入活动状态。

五、Provider 类型详解

5.1 类型总览

Riverpod 3.0 将旧版 provider(StateProvider、FutureProvider 等)标记为遗留(legacy),推荐使用基于 Notifier 的新类型。

推荐使用的 Provider 类型

Provider 类型 描述 适用场景
Provider 只读同步,返回固定或计算值 常量配置、依赖注入、简单计算
NotifierProvider 可变同步状态 计数器、表单输入、简单 UI 状态
AsyncNotifierProvider 可变异步状态,内置 loading/error/data API 调用、数据库查询
StreamNotifierProvider 可变流式状态 WebSocket、实时数据流

遗留类型(仅迁移参考)

  • StateProvider → 迁移到 NotifierProvider
  • FutureProvider → 迁移到 AsyncNotifierProvider
  • StreamProvider → 迁移到 StreamNotifierProvider
  • StateNotifierProvider → 迁移到 NotifierProvider

5.2 选择决策流程

选择 provider 时,按以下流程决策:

  1. 数据是同步还是异步? → 同步用 NotifierProvider,异步用 AsyncNotifierProvider
  2. 数据是只读还是可变? → 只读用 Provider,可变用 Notifier
  3. 是否需要复杂业务逻辑? → 使用 Notifier 类封装
  4. 是否需要自动释放或参数化? → 使用 .autoDispose.family 修饰符

5.3 代码示例

Provider(只读依赖注入)

final apiClientProvider = Provider<ApiClient>((ref) {
  return ApiClient();
});

适合存放不会主动变化的对象,如 Repository、Service、配置项等。

NotifierProvider(同步可变状态)

final cartProvider = NotifierProvider<CartNotifier, List<String>>(
  CartNotifier.new,
);

class CartNotifier extends Notifier<List<String>> {
  @override
  List<String> build() => [];

  void add(String item) {
    state = [...state, item];
  }

  void remove(String item) {
    state = state.where((i) => i != item).toList();
  }
}

适合购物车、表单状态、筛选条件等场景。

AsyncNotifierProvider(异步可变状态)

@riverpod
class UserProfile extends _$UserProfile {
  @override
  Future<User> build() async {
    final api = ref.watch(apiClientProvider);
    return await api.fetchUser();
  }

  Future<void> updateName(String name) async {
    state = const AsyncValue.loading();
    try {
      final api = ref.watch(apiClientProvider);
      final updated = await api.updateUserName(name);
      state = AsyncValue.data(updated);
    } catch (e, st) {
      state = AsyncValue.error(e, st);
    }
  }
}

使用 AsyncNotifier 时,ref.watch 会返回 AsyncValue,内置 loading、error、data 三种状态。

六、ref 的三种用法

ref 是 Riverpod 中连接 Provider 和 UI 的桥梁,提供了三种核心方法。

6.1 ref.watch —— 声明式监听

ref.watch 用于在 build 方法中声明式地监听 provider 状态,当状态变化时自动重建 UI。

class CounterWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // watch 会建立响应式关系,状态变化时自动重建
    final count = ref.watch(counterProvider);
    return Text('Count: $count');
  }
}

关键规则ref.watch 只能在 build 方法的根部使用,不能在异步回调(如 onPressed)中调用。

6.2 ref.read —— 一次性读取

ref.read 用于一次性读取 provider 的当前状态,不建立监听关系

// 在按钮点击事件中调用方法
onPressed: () {
  // read 只读一次,不监听变化
  ref.read(counterProvider.notifier).increment();
}

使用场景:事件处理中调用 Notifier 的方法、不需要 UI 响应的场景。

注意:官方建议尽可能使用 ref.watch 而非 ref.read,因为 ref.watch 更安全。

6.3 ref.listen —— 副作用监听

ref.listen 用于监听状态变化并执行副作用(如导航、弹窗、日志等),不会触发 UI 重建

ref.listen(authProvider, (previous, next) {
  if (previous != next && next.isLoggedIn) {
    // 用户登录后跳转
    Navigator.pushNamed(context, '/home');
  }
});

七、高级特性

7.1 代码生成(@riverpod)

Riverpod 官方推荐使用 @riverpod 注解配合代码生成,可以大幅减少样板代码。

安装依赖

dependencies:
  flutter_riverpod: ^3.3.1
  riverpod_annotation: ^4.0.2

dev_dependencies:
  build_runner: ^2.14.0
  riverpod_generator: ^4.0.2

使用示例

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'counter.g.dart';

@riverpod
int counter(CounterRef ref) => 0;

只需这样简单的注解,Riverpod 就会自动生成 counterProvider

运行代码生成:

dart run build_runner watch --delete-conflicting-outputs

7.2 family(参数化 Provider)

family 允许根据参数创建多个 Provider 实例,适用于需要参数的场景。

@riverpod
Future<User> user(UserRef ref, {required String id}) async {
  final api = ref.watch(apiClientProvider);
  return await api.fetchUser(id);
}

// 使用时传入参数
final user = ref.watch(userProvider(id: '123'));

注意:参数必须具有一致的 hashCode==,建议使用原始类型(bool/int/double/String)或不可变对象。

7.3 autoDispose(自动释放)

autoDispose 让 provider 在不再被监听时自动销毁状态,释放内存。

使用代码生成时,autoDispose 默认开启:

@riverpod
@AutoDispose()  // 默认就是 autoDispose
Future<List<Post>> posts(PostsRef ref) async {
  final api = ref.watch(apiClientProvider);
  return await api.fetchPosts();
}

如需保持状态存活,可以使用 keepAlive

7.4 select(精细化重建)

select 允许只监听对象的特定属性,减少不必要的 UI 重建。

// 只监听 user 的 name 属性,其他属性变化不会触发重建
final userName = ref.watch(userProvider.select((user) => user.name));

八、实战案例

8.1 计数器(最简示例)

定义 Provider

@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;

  void increment() => state++;
  void decrement() => state--;
}

使用 Provider

class CounterPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    
    return Column(
      children: [
        Text('Count: $count'),
        ElevatedButton(
          onPressed: () => ref.read(counterProvider.notifier).increment(),
          child: Text('+'),
        ),
        ElevatedButton(
          onPressed: () => ref.read(counterProvider.notifier).decrement(),
          child: Text('-'),
        ),
      ],
    );
  }
}

8.2 TodoList(带异步操作的复杂状态)

定义 Todo 模型和 Notifier

class Todo {
  final String id;
  final String title;
  final bool completed;
  
  Todo({required this.id, required this.title, this.completed = false});
}

@riverpod
class TodoList extends _$TodoList {
  @override
  List<Todo> build() => [];

  void add(String title) {
    state = [
      ...state,
      Todo(id: DateTime.now().toString(), title: title),
    ];
  }

  void toggle(String id) {
    state = state.map((todo) {
      if (todo.id == id) {
        return Todo(id: todo.id, title: todo.title, completed: !todo.completed);
      }
      return todo;
    }).toList();
  }

  void remove(String id) {
    state = state.where((todo) => todo.id != id).toList();
  }
}

// 派生状态:已完成数量
@riverpod
int completedCount(CompletedCountRef ref) {
  final todos = ref.watch(todoListProvider);
  return todos.where((todo) => todo.completed).length;
}

UI 使用

class TodoPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final todos = ref.watch(todoListProvider);
    final completed = ref.watch(completedCountProvider);
    
    return Column(
      children: [
        Text('已完成: $completed / ${todos.length}'),
        Expanded(
          child: ListView.builder(
            itemCount: todos.length,
            itemBuilder: (ctx, i) {
              final todo = todos[i];
              return ListTile(
                title: Text(todo.title),
                leading: Checkbox(
                  value: todo.completed,
                  onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),
                ),
                trailing: IconButton(
                  icon: Icon(Icons.delete),
                  onPressed: () => ref.read(todoListProvider.notifier).remove(todo.id),
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}

8.3 异步 API 调用

定义异步 Provider

@riverpod
class UserList extends _$UserList {
  @override
  Future<List<User>> build() async {
    final api = ref.watch(apiClientProvider);
    return await api.fetchUsers();
  }

  Future<void> refresh() async {
    // 重新执行 build
    state = await build();
  }
}

UI 中使用 AsyncValue

class UserListPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final usersAsync = ref.watch(userListProvider);
    
    return usersAsync.when(
      data: (users) => ListView.builder(
        itemCount: users.length,
        itemBuilder: (ctx, i) => Text(users[i].name),
      ),
      loading: () => const CircularProgressIndicator(),
      error: (err, st) => Text('Error: $err'),
    );
  }
}

AsyncValuewhen 方法统一处理了 loading、data、error 三种状态。

8.4 依赖注入与组合

Riverpod 的 ref.watch 天然支持 Provider 之间的依赖组合:

@riverpod
ApiClient apiClient(ApiClientRef ref) {
  return ApiClient();
}

@riverpod
class UserRepository extends _$UserRepository {
  @override
  Future<List<User>> build() async {
    final api = ref.watch(apiClientProvider);
    return await api.fetchUsers();
  }
}

@riverpod
class DashboardData extends _$DashboardData {
  @override
  Future<DashboardData> build() async {
    // 组合多个 Provider
    final users = await ref.watch(userRepositoryProvider.future);
    final stats = await ref.watch(statsProvider.future);
    return DashboardData(users: users, stats: stats);
  }
}

8.5 单元测试

Riverpod 的测试非常简洁,无需 Widget 树:

test('TodoList 添加和切换', () {
  final container = ProviderContainer();
  addTearDown(container.dispose);
  
  // 添加 todo
  container.read(todoListProvider.notifier).add('任务A');
  expect(container.read(todoListProvider).length, 1);
  
  // 切换完成状态
  final id = container.read(todoListProvider).first.id;
  container.read(todoListProvider.notifier).toggle(id);
  expect(container.read(completedCountProvider), 1);
});

test('Mock 依赖做测试', () {
  final container = ProviderContainer(
    overrides: [
      // 替换真实 Repository 为假的
      userRepositoryProvider.overrideWithValue(FakeUserRepository()),
    ],
  );
  addTearDown(container.dispose);
  
  // 测试逻辑...
});

核心优势:ProviderContainer 在纯 Dart 环境下工作,不需要 pumpWidget,测试跑得快;overrides 让你能替换任何一层依赖。

九、最佳实践

9.1 Provider 定义规范

  • Provider 应声明为 final 全局常量
  • 使用 @riverpod 注解配合代码生成,而非手动编写 Provider
  • 按功能模块组织 Provider 文件

9.2 ref 使用规范

方法 使用场景 注意事项
watch build 方法中声明式监听 只能在 build 方法根部使用
read 事件处理中读取状态 避免在 build 中用于渲染
listen 状态变化时执行副作用 不触发 UI 重建

9.3 状态管理分层

推荐按以下层次组织状态:

  • UI State:页面级临时状态(表单输入、弹窗显示等)
  • Domain State:业务领域状态(用户信息、购物车等)
  • Cache State:缓存数据(API 响应缓存等)

9.4 内存管理

  • 为临时状态(如详情页数据)开启 autoDispose
  • 使用 family 时务必开启 autoDispose,避免参数组合过多导致内存泄漏
  • TearDown 中 dispose 测试用的 ProviderContainer

9.5 项目结构

推荐按功能模块组织:

lib/
├── core/           # 共享资源(entities, services, utils)
├── features/       # 功能模块
│   ├── auth/
│   │   ├── providers/    # Provider 定义
│   │   ├── models/       # 数据模型
│   │   └── pages/        # UI 页面
│   └── home/
│       └── ...
└── main.dart

十、总结

Riverpod 作为 Flutter 生态中最先进的状态管理方案之一,其核心设计理念可以总结为:

Provider 是“带缓存的函数”,通过 ref 组合依赖、自动失效;UI 通过 ref.watch 订阅,状态变化时自动重建。

关键 takeaways

  1. 不依赖 BuildContext:可在任何地方使用,包括纯 Dart 类、测试中
  2. 编译时安全:Provider 作为常量,拼写错误直接报错
  3. 响应式缓存:自动管理生命周期,无监听时自动释放
  4. 异步原生支持:AsyncNotifier + AsyncValue 统一处理 loading/error/data
  5. 测试友好:ProviderContainer + overrides 轻松 mock

版本提示

  • 当前最新稳定版本:flutter_riverpod: ^3.3.1
  • Riverpod 3.0 推荐使用 Notifier/AsyncNotifier,旧版 Provider 类型已标记为遗留
  • 官方推荐使用 @riverpod 注解 + 代码生成

学习路径建议

  1. 入门:掌握 ProviderScope、ConsumerWidget、ref.watch/read
  2. 进阶:学习 Notifier/AsyncNotifier、AsyncValue 的 when 方法
  3. 高级:掌握 family、autoDispose、代码生成、单元测试

掌握 Riverpod,你就掌握了 Flutter 状态管理的未来方向。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容