Riverpod 3.0.0 版本中 Provider 类型选择指南

665 阅读4分钟

Riverpod 3.0.0(2025 年 9 月发布)是 Flutter 状态管理库的重大更新,简化了 API,引入了“mutations”功能(处理副作用,如加载/错误状态),统一了更新过滤机制(全部使用 == 比较),并默认支持自动重试失败的 provider。关键更新:Riverpod 3.0.0 不再强制要求使用 @riverpod 注解和 riverpod_generator,可以直接编写 Notifier 类,减少 boilerplate 代码。旧版 provider(如 StateProviderFutureProvider 等)被标记为遗留(legacy),需从 flutter_riverpod/legacy.dart 导入,官方推荐迁移到基于 Notifier 的新 provider 类型。这些新类型更统一、可测试,且支持手动编写或代码生成(仍可选 @riverpod)。

选择 provider 时,考虑以下因素:

  • 同步 vs 异步:数据是立即可用(同步)还是需要等待(Future/Stream)?
  • 只读 vs 可变:状态是否需要外部修改?
  • 复杂逻辑:是否涉及业务规则、依赖注入或副作用?
  • 性能与清理:是否需要 .autoDispose(自动销毁未使用的 provider)或 .family(参数化 provider)修饰符?

以下是 Riverpod 3.0.0 中推荐的 provider 类型及选择指南,遗留类型仅作迁移参考。内容基于官方文档和社区实践。

主要 Provider 类型

Provider 类型描述与用途适用场景示例优缺点简析
Provider只读同步 provider,返回固定或计算值。不支持状态修改。常量配置、简单计算(如字符串格式化)、依赖注入。优点:轻量、易缓存;缺点:不可变,无法处理异步。
NotifierProvider<T, Notifier>可变同步状态,使用 Notifier 类管理内部状态。支持 state 更新。计数器、表单输入、简单 UI 状态(如开关)。优点:简单、可测试;缺点:不适合复杂异步逻辑。推荐用于简单可变状态。
AsyncNotifierProvider<T, AsyncNotifier<T, T>>可变异步状态,使用 AsyncNotifier 处理 Future 值。内置加载/错误/数据状态。API 调用、数据库查询,需要更新异步数据。优点:统一处理异步(loading/error),支持 mutations;缺点:稍复杂。推荐用于大多数异步场景。
StreamNotifierProvider<T, StreamNotifier<T, T>>可变流式状态,使用 StreamNotifier 处理 Stream 值。实时更新。实时数据(如 WebSocket、动画流)、事件监听。优点:响应式流处理;缺点:仅限 Stream 数据。推荐用于实时更新。
遗留:StateProvider (从 legacy.dart)简单可变状态(如字符串/整数)。不推荐。临时简单状态(迁移时使用)。优点:快速;缺点:缺乏类型安全,官方弃用。迁移到 NotifierProvider。
遗留:FutureProvider (从 legacy.dart)只读异步 Future 值。不推荐。一次性异步加载(迁移时使用)。优点:简单异步;缺点:不可变、无内置重试。迁移到 AsyncNotifierProvider。
遗留:StreamProvider (从 legacy.dart)只读流式 Stream 值。不推荐。实时流(迁移时使用)。优点:流支持;缺点:不可变。迁移到 StreamNotifierProvider。
遗留:StateNotifierProvider<T, S> (从 legacy.dart)复杂可变状态,使用 StateNotifier 类。不推荐。业务逻辑类(如用户认证)。优点:强大;缺点:冗余。迁移到 NotifierProvider(简化版)。

如何选择 Provider?(决策流程)

  1. 数据是同步且只读?
    使用 Provider
    示例:

    final configProvider = Provider<String>((ref) => 'Hello');
    

    适用:静态配置、计算值。UI 中使用:final value = ref.watch(configProvider);

  2. 数据是同步且可变?
    使用 NotifierProvider
    示例:

    class Counter extends Notifier<int> {
      @override
      int build() => 0;  // 初始状态
      void increment() => state++;  // 修改状态
    }
    final counterProvider = NotifierProvider<Counter, int>(() => Counter());
    

    适用:计数器、表单输入。UI 中使用:final count = ref.watch(counterProvider);

  3. 数据是异步(Future)且可能需要更新?
    使用 AsyncNotifierProvider
    示例:

    class UserData extends AsyncNotifier<User> {
      @override
      Future<User> build(String userId) async {
        return await fetchUser(userId);  // 异步加载
      }
      Future<void> refresh() async => state = const AsyncLoading();  // 更新
    }
    final userProvider = AsyncNotifierProvider<UserData, User>((ref) => UserData());
    

    适用:API 调用、数据库查询。自动处理 AsyncValue(loading/data/error)。UI 中使用:final user = ref.watch(userProvider);

  4. 数据是流式(Stream)且实时更新?
    使用 StreamNotifierProvider
    示例:

    class MessageStream extends StreamNotifier<List<Message>> {
      @override
      Stream<List<Message>> build() => firestore.collection('messages').snapshots();
    }
    final messageProvider = StreamNotifierProvider<MessageStream, List<Message>>((ref) => MessageStream());
    

    适用:实时数据(如 Firestore 流、WebSocket)。

  5. 复杂业务逻辑或副作用?
    使用 mutations(3.0 新功能)。
    示例:

    final submitMutation = mutationProvider((ref, FormData data) async {
      await api.submit(data);
    });
    

    适用:表单提交、一次性操作,显示加载/错误状态而不中断 provider。

  6. 遗留类型?
    仅用于迁移旧代码,逐步替换为 Notifier 系列。@riverpod 注解可选,若使用可运行 flutter packages pub run build_runner build 生成代码。

最佳实践

  • 无需注解:Riverpod 3.0.0 允许直接编写 Notifier/AsyncNotifier 类,无需 @riverpodriverpod_generator,但仍支持代码生成以减少 boilerplate。
  • 修饰符
    • .autoDispose:自动清理未使用的 provider,防止内存泄漏。
    • .family:参数化 provider(如 userProvider.family(userId))。
  • 测试:Notifier 类可独立测试,无需 Flutter 环境。
  • 性能:更新过滤统一使用 ==,减少不必要重建。
  • 错误处理:异步 provider 抛 ProviderException,支持自动重试。
  • 迁移:从 2.x 迁移时,替换遗留 provider,检查错误处理逻辑。