Skip to content

Riverpod 的核心:到底什么是“提供者” (Provider)?

一句话总结

“提供者” (Provider) 是 Riverpod 的灵魂。可以说,我们用 Riverpod,就是冲着它的 Provider 来的。


“提供者” (Provider) 是 Riverpod 的灵魂。可以说,我们用 Riverpod,就是冲着它的 Provider 来的。

那么,Provider 到底是个啥?

简单来说,Provider 就是一个 “带了点额外功能的、有记忆的函数”

这是什么意思呢?意思是,Provider 本质上是个函数,但它很聪明,当你用相同的参数去调用它时,它不会每次都重新计算,而是直接给你上次算好的、缓存起来的结果。

最常见的应用场景就是网络请求。

想象一下,我们有一个从服务器获取用户信息的函数:

dart
Future<User> fetchUser() async {
  final response = await http.get('https://api.example.com/user/123');
  return User.fromJson(response.body);
}

这个普通函数有个问题:如果我们在好几个 Widget 里都想用它,我们就得自己手动管理请求回来的数据,比如自己搞个缓存,还得想办法把这个数据共享给所有需要它的 Widget。太麻烦了!

这时候,Provider 就闪亮登场了。Provider 就像是给普通函数套上了一个“超能外套”,它会自动帮你缓存函数的结果,并让多个 Widget 轻松共享同一个数据:

dart
// 这就是 fetchUser 函数的“超能版”
// 它会自动缓存结果,多次调用也只会请求一次
@riverpod
Future<User> user(Ref ref) async {
  final response = await http.get('https://api.example.com/user/123');
  return User.fromJson(response.body);
}

除了基本的缓存功能,Provider 还附带了一堆让它更强大的“超能力”:

  • 聪明的缓存管理

    特别是 ref.watch 这个功能,能让你把好几个缓存组合起来,当其中一个变化时,它能自动更新其他依赖它的缓存。

  • 自动“垃圾回收”

    当一个 Provider 不再被任何地方使用时,它会自动清理自己占用的资源,非常省心。

  • 无缝的数据绑定

    有了 Provider,你再也不需要写那些丑陋的 FutureBuilderStreamBuilder 了。

  • 贴心的错误处理

    Provider 会自动帮你捕获函数里的错误,并把错误状态暴露给界面,方便你展示错误提示。

  • 强大的模拟功能

    在写测试或者其他场景下,你可以轻松地“模拟”任何一个 Provider 的返回值。

  • 离线数据持久化 (实验性功能)

    你可以把 Provider 的结果存到手机磁盘上,下次打开 App 时能自动加载,实现离线可用。

  • “变更”操作支持 (实验性功能)

    比如提交表单这种操作,Provider 提供了一套内置方案,让你能方便地在界面上展示“加载中”或“提交失败”等状态。

Provider 家族一共有 6 位成员:

同步异步 (Future)流 (Stream)
只读ProviderFutureProviderStreamProvider
可读可写NotifierProviderAsyncNotifierProviderStreamNotifierProvider

这么多,是不是有点眼花缭乱?别急,我们来拆解一下。

同步 vs 异步 vs 流:

这三列其实就对应了 Dart 语言里三种最基本的函数类型。

dart
// 返回一个普通的值
int synchronous() => 0;
// 异步返回一个值
Future<int> future() async => 0;
// 返回一个持续不断的值流
Stream<int> stream() => Stream.value(0);

只读 vs 可读可写:

默认情况下,Provider 的值是“只读”的,界面不能直接修改它。而名字里带 “Notifier” 的变体,则允许从外部修改它的值。

这有点像一个变量是私有的还是公开的:

dart
// “只读”的 Provider 就像这样,只能在内部修改
var _state = 0;
int get state => _state;

对比一下:

dart
// “可读可写”的 Provider 就像这样,谁都能改
var state = 0;

小贴士

你也可以把“只读”和“可读可写”的 Provider,粗略地理解成 Flutter 里的 StatelessWidgetStatefulWidget

当然这个比喻不完全准确,因为 Provider 不是 Widget,而且它们都存着“状态”。但核心思想是类似的:“一个对象,状态不可变” vs “两个对象,状态可变”。

如何创建一个 Provider

我们应该把 Provider 定义在代码的“顶层”,也就是说,不要把它放在任何类或者函数里面。

创建 Provider 的语法,取决于你想创建的是“只读”的还是“可读可写”的,也就是上面表格里的分类。

这里我们以推荐的代码生成方式为例 创建“只读”的,用@riverpod注解函数

dart
@riverpod
Result myFunction(Ref ref) {
  // <你的逻辑代码放这里>
}

创建可读可写的,用@riverpod注解类并拓展 _$类名,以便在你的内部写方法 中访问ref:

dart
@riverpod
class MyNotifier extends _$MyNotifier {
  @override
  Result build() {
    <your logic here>
  }
  <your methods here> //你的内部写方法
}
元素说明
注解所有用代码生成的 Provider 都必须用 @riverpod@Riverpod() 来标记。这个注解可以放在全局函数或类上。通过它,我们可以对 Provider 进行一些配置,比如 @Riverpod(keepAlive: true) 就可以让它不被自动销毁。
被注解的函数函数的名字很重要,它决定了你将来怎么使用这个 Provider。比如函数名叫 myFunction,生成后你就可以用 myFunctionProvider 来访问它。这个函数 必须Ref 作为它的第一个参数。除此之外,你可以加任意多的参数,也可以用泛型。函数体里可以写你的业务逻辑,比如网络请求。这个函数只会在 Provider 第一次被读取时执行一次,之后再读取就会直接返回缓存的结果。
Ref这是一个用来和其他 Provider 互动的小助手。每个 Provider 都有一个 ref 对象,要么是作为函数的参数传入,要么是作为 Notifier 的一个属性。

小贴士

你可以随心所欲地创建任意多个 Provider。和 package:provider 不同,Riverpod 允许你创建好几个类型相同的 Provider,完全没问题:

dart
@riverpod
String city(Ref ref) => '伦敦';
@riverpod
String country(Ref ref) => '英格兰';

这两个 Provider 都返回 String 类型,但它们不会互相冲突。

如何使用 Provider

Provider 自己本身什么也不做,就像 Widget 如果不被 runApp,也只是个普通的类一样。

你可以把 Provider 理解为对“状态”的一种描述,就像 Widget 是对“UI”的一种描述。

要真正使用 Provider,你需要一个叫做 ProviderContainer 的东西。简单来说,就是在你的 App 最外层套上一个 ProviderScope

dart
void main() {
  runApp(
    // 把你的 App 包裹起来
    ProviderScope(
      child: MyApp(),
    ),
  );
}

搞定这一步之后,你就可以通过一个叫 Ref 的小助手来和你的 Provider 们互动了。

简单来说,有两种方式可以拿到 Ref

  • 在 Provider 内部

    就像我们前面看到的,ref 会作为参数传给 Provider 函数,或者作为 Notifier 的一个属性。这让 Provider 之间可以互相通信。

  • 在 Widget 树中

    你需要使用一个特殊的 Widget,叫做 Consumer。它就像一座桥梁,连接了 Widget 世界和 Provider 世界,并递给你一个 WidgetRef 对象。

举个例子,假设我们有个 helloWorldProvider,它只返回一个字符串 "Hello world"。在 Widget 里可以这样用它:

dart
class Example extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 使用 Consumer Widget
    return Consumer(
      builder: (context, ref, child) {
        // 通过 ref.watch 来获取 provider 的值
        final helloWorld = ref.watch(helloWorldProvider);

        // 然后在 UI 中使用这个值
        return Text(helloWorld);
      },
    );
  }
}