Riverpod3.0 基本概念之 Ref

43 阅读2分钟

Ref是与Provider交互的基本方式。

对Provider而言,Ref类似于Flutter中的BuildContext。使用ref可以完成的部分工作如下:

  • 对一个Provider执行读取与观测操作。

  • 检查Provider是否已加载

  • 重置Provider的状态

除此之外,Ref还可以让Provider观测自身状态的生命周期。就像Widget中的 initSatedispose 方法,Provider中的相应方法包括 onDisposeonCancel 等等。

获取一个Ref对象的方式

在应用的不同位置,就会有不同的获取Ref对象的方法。

Provider默认就可以获取一个Ref对象。它做为一个参数存在于初始化方程中,也作为一个属性存在与Notifier类中。


final myProvider = Provider<int>((ref) {

// 在这里就可以使用ref对象

});

  


final myNotifierProvider = NotifierProvider<MyNotifier, int>(MyNotifier.new);

  


class MyNotifier extends Notifier<int> {

@override

int build() {

// 在notifier的任何地方都可以使用 this.ref

ref.watch(someProvider);

}

}

要在Widget中获取Ref对象,就需要使用Consumer


Consumer(

builder: (context, ref, _) {

// 此处可以访问ref对象

finl value = ref.watch(myProvider);

return Text('$value');

}

);

如果既不在Widget中,也不在Provider中,那该如何获取Ref对象?

即使不在Widget中也不在Provider中,只要能跟Widget或Provider建立一种哪怕非常弱的联系,就可以获取一个Ref对象。

在这种情况下,只需要先从Widget或者Provider中获取Ref对象,然后将这个对象传入相应的方程或对象中,如此即可。


void myFunction(WidgetRef ref) {

// 此处接受一个Ref对象

}

  


Consumer(

builder: (context, ref, _) {

return ElevatedButton(

onPressed: () => myFunction(ref); // 将Ref对象传入方程

child: Text('Click me'),

);

}

);

使用Ref与Provider交互

通常将与Provider的交互操作概括为如下两类:

  • 监听Provider的状态

  • 修改Provider的状态(比如重置或更新)

监听状态

Riverpod提供了两种监听方式:

  • Ref.watch 声明式监听。最常用的监听方式,开发首选。

  • Ref.listen 手动监听。标准的“addListen”式监听,功能更强大,但使用起来也更复杂。

如下示例,每秒更新一次Provider:


final tickProvider = NotifierProvider<Tick, int>(Tick.new);

class Tick extends Notifier<int> {

@overrride

int build() {

final timer = Timer.periodic(Duration(seconds: 1), (_) => state++);

ref.onDispose(timer.cancel);

return 0;

}

}

Ref.watch模式

该监听模式是Riverpod的特色功能。它可以将多个Provider无缝结合,并在Provider状态改变时便捷地更新UI。

使用Ref.watch的方式与在Flutter中使用InheritedWidget的方式相似。在Flutter中,当调用Theme.of(context)时,对应Widget就会订阅Theme,并在Theme发生改变时重建Widget。与之相似,当调用ref.watch(myProvider)时,对应的Widget或Provider就会订阅myProvider,并在myProvider发生改变时重建。

如下代码所示,当Provider实例Tick更新时,Consumer也会自动更新:


Consumer(

builder: (context, ref, _) {

final tick = ref.watch(tickProvider);

return Text('Tick: $tick');

}

);

Ref.watch最有意思的地方在于,Provider也可以使用它。

例如,我们可以创建一个Provider,让这个Provider返回“is tick divisible by 4?”。


final isDivisibleBy4Provider = Provider<bool>(

(ref) {

final tick = ref.watch(tickProvider);

return tick % 4 == 0;

}

);

然后,在UI界面监听这个新的Provider对象:


Consumer(

builder: (context, ref, _) {

final isDivisibleBy4 = ref.watch(isDivisibleBy4Provider);

return Text('Can tick be divided by 4? ${isDivisibleBy4}');

},

);

现在,UI就只会在Boolean值发生改变时才会更新,而不是每秒都更新。

Ref.listen模式

这种模式需要自己动手做一些配置,与ChangeNotifieraddListener方法相似,也与Stream.listen方法相似。

如果想要在provider的状态发生改变时执行其他操作,那么这个方法就很有用。这些“其他操作”包括显示一个对话框、跳转到其他页面、打印一条信息等等。


final exampleProvider = Provider<int>((ref) {

ref.listen(

tickProvider, (previous, next) {

// tickProvider发生改变时,如下方法就会被调用

print('Tick changed from $previous to $next');

}

);

  


return 0;

});


Consumer(

builder: (contex, ref, _) {

ref.listen(

tickProvider, (previous, next) {

// tickProvider发生改变时,如下方法就会被调用

print('Tick changed from $previous to $next');

}

);

  


return Text('Listening to tick changes');

}

);

重置状态

使用Ref.invalidate可以重置Provider的状态。

该方法会通知Riverpod清理当前状态,并在下一次读取Provider时重新计算。

如下代码会将tick重置为0:


Consumer(

builder: (context, ref, _) {

return ElevatedButton(

onPressed: () {

// 重置tickProvider,

// tick会重新从0开始计时

ref.invalidate(tickProvider);

},

child: Text('Reset tick'),

);

},

);

提示

如果需要在重置之后就获取一个新的状态,可以调用Ref.read方法:

ref.invalidate(tickProvider);

final newTick = ref.read(tickProvider);

也可以使用Ref.refresh方法,一步完成重置状态并读取新状态的操作。

final newTick = ref.refresh(tickProvider);

以上两段代码是等价的。Ref.refresh是对Ref.invalidateRef.read的封装。

在用户交互操作中与Provider的状态进行交互

最后一个示例是在点击按钮时与Provider的状态实现交互。在这个场景中,不对状态进行监听操作。所以,会使用到Ref.read方法。

在点击按钮执行任务时,可以安全调用Ref.read方法。如下示例展示在点击按钮时打印当前tick值:


Consumer(

builder:(context, ref, _) {

return ElevatedButton(

onPressed: () {

// 读取当前tick值

final tick = ref.read(tickProvider);

print('Current tick: $tick');

},

child: Text('Print Tick'),

);

  


},

);

危险操作

不要试图使用Ref.read替换Ref.watch优化代码,这样晖让代码变得不稳定,因为Provider行为的改变会引起UI与Provider的状态的不同步。

要选择Ref.watchslelect方法:

Consumer(

builder:(context, ref, _) {

// 不要这样操作。不要使用read作为忽视改变的手段

final tick = ref.read(tickProvider);

// 推荐操作。使用watch方法监听改变。

// 此处不会成为软件的瓶颈,不要在这里过度优化

final tick = ref.watch(tickProvider);

// 推荐操作。使用select方法,只监听state中指定数据

final isEven = ref.watch(tickProvider.select((tick) => tick.isEven),);

}

);

监听生命周期事件

Ref中有一个针对Provider的功能,就是监听生命周期事件。这些事件与Flutter控件中的initStatedispose以及其他生命周期方法类似。

生命周期监听方法使用“addListener”风格的API进行注册。监听方法是一些名字以on开头的方法,比如onDisposeonCancel


final counterProvider = Provider<int> ((ref) {

ref.onDispose(

// 清理provider时会调用该方法

print('Counter provider is being disposed);

);

  


return 0;

});

提示

不需要手动注销这些监听方法。

Provider重置时,Riverpod会自动清理这些方法。

使用这些方法的返回值也可以手动注销这些方法。

final unregister = ref.onDispose(() {

print('This will never be called);

});

// onDispose方法在这里被注销

unregister();