SuperAPI AI 工具集

riverpod-from-provider

编程与开发

查看项目

更新于 10-16 04:14

技能介绍

用途

本技能用于指导将 Flutter 项目从 package:provider 迁移到 Riverpod。Riverpod 是 Provider 的后继方案,针对 InheritedWidget 的局限做了改进,迁移的目标是让状态定义、读取、组合、生命周期与测试都更清晰、更安全。

为什么迁移

资料列出了 Riverpod 相对 Provider 的主要优势:

  • 同类型限制:Provider 无法在同一棵树中放置两个 Provider<Item>(只会找到最近的一个)。Riverpod 没有此限制,provider 由变量而非类型标识。
  • 组合 provider:ProxyProvider 繁琐且易出错。在 Riverpod 中,在 provider 内部使用 ref.watch 即可依赖其他 provider,组合更直接。
  • AsyncValue:Riverpod 能在加载时暴露之前的数据(例如显示旧列表加加载指示器),Provider 没有干净的等价方案。
  • 安全性:Provider 可能在运行时抛出 ProviderNotFoundException,Riverpod 从设计上避免了这一点。
  • 销毁:Provider 无法在消费者停止监听时做出反应,作用域处理棘手。Riverpod 提供 autoDispose 和 ref.keepAlive,生命周期与缓存更明确。
  • 参数:Riverpod 的 .family 提供类型安全、带参数的 provider,每个参数有独立状态;配合 autoDispose,未使用时状态会被销毁。Provider 中实现等价功能不切实际。
  • 测试:使用 Provider 时每个测试都要重新定义 provider;使用 Riverpod 可通过 overrides 进行 mock。
  • 副作用:Riverpod 提供 ref.listen 来响应变化(例如导航、snackbar),Provider 没有内置等价物。

主要 API 变化:使用 ConsumerWidget(及 WidgetRef)替代 StatelessWidget,使用 ref.watch / ref.read 替代 context.watch / context.read。

快速开始

  • 阅读 Riverpod 入门指南(riverpod-getting-started),并尝试一个小示例。
  • 增量迁移。可以让 Provider 和 Riverpod 并存(必要时使用 import 别名)。

从 ChangeNotifierProvider 开始

保留现有的 ChangeNotifier 类,用 Riverpod 的 ChangeNotifierProvider 包装它们:

final myNotifierProvider = ChangeNotifierProvider<MyNotifier>((ref) => MyNotifier());

在根部使用 ProviderScope。在使用该 provider 的地方(例如 ConsumerWidget 中)用 ref.watch 替换 context.watch。不必立即把每个 ChangeNotifier 都转换为 Notifier。

从叶子开始

先迁移没有依赖的 provider(“叶子”),再迁移依赖它们的 provider。在依赖项迁移完成之前,避免迁移 ProxyProvider。

一次一个 provider

一次迁移并测试一个 provider。完整迁移一个 ChangeNotifier 意味着:(1) 转换为 Notifier + NotifierProvider,(2) 把每一处针对它的 context.watch 替换为 ref.watch。

ProxyProvider → ref.watch

在 Riverpod 中,组合 provider 通过在另一个 provider 内部使用 ref.watch 完成:

final labelProvider = Provider<String>((ref) { final userIdNotifier = ref.watch(userIdNotifierProvider); return 'The user ID is ${userIdNotifier.userId}'; });

对于有状态的组合对象(如 ChangeNotifierProxyProvider),在 provider 中使用 ref.listen 来响应另一个 provider 并更新你的 notifier。

急切初始化

Riverpod 的 provider 是惰性的。要在启动时预热数据,可在根部 watch 该 provider(例如在 ProviderScope 下、以你的 app 作为 child 返回的 Consumer 中)。参见 riverpod-eager-initialization。

代码生成

代码生成不会生成 ChangeNotifierProvider。迁移期间可以使用一个小扩展(listenAndDisposeChangeNotifier),在 @riverpod 下暴露 ChangeNotifier;一旦切换到 Notifier,就移除该扩展。参见官方快速开始中的代码片段。

Provider 与 Riverpod 对比

定义 provider

  • Provider:provider 是 widget(例如在 MultiProvider 内部)。
  • Riverpod:provider 是顶层 final 变量。定义不依赖 widget 树。在应用根部添加 ProviderScope。

读取

  • Provider:context.watch<T>()、context.read<T>()、context.select<T,R>(...)。
  • Riverpod:使用 ConsumerWidget(或 Consumer)获取 WidgetRef ref;然后 ref.watch(provider)、ref.read(provider)、ref.watch(provider.select(...))。

在 build 中使用 watch,在事件处理器中使用 read。心智模型与 Provider 相同。

Consumer

Riverpod 有 Consumer,签名为 (context, ref, child)。不需要 Consumer2、Consumer3 等:在一个 builder 中多次调用 ref.watch 即可。

作用域 vs family + autoDispose

Provider 使用作用域来销毁状态或实现每页状态。在 Riverpod 中:

  • autoDispose 在没有监听者时销毁状态(ref.onCancel / ref.onDispose)。
  • .family 提供参数化 provider(每个参数一个状态)。与 autoDispose 配合使用可避免无界缓存。

因此:用 .family 表示“每个 X 一个状态”,用 .autoDispose 表示“未使用时销毁”,而不是使用作用域。

详见 riverpod-getting-started、riverpod-providers、riverpod-family 和 riverpod-auto-dispose。