在项目中,遇到了各种Consumer,刚开始的时候还不知道他们有什么区别,后面通过一些开发实践逐渐摸清楚了他们之间的区别和使用场景。
首先,这些Consumer来源于riverpod这个库,主要是提升开发者对provider的使用,便于获取provider,实现相关的状态管理。
1. ConsumerWidget
是一个StatefulWidget,但是不需要我们去实现相关的Widget和State。内部已经帮我们封装实现好了。使用时,只要继承ConsumerWidget,实现Widget build(BuildContext context, WidgetRef ref);函数即可。然后通过ref获取需要的provider
2. Consumer
是对ConsumerWidget的进一步封装。对于一些Widget我们不想去新建一个类来继承ConsumerWidget,那我们可以使用Consumer来包裹Widget。
例子:
Consumer(
builder: (context, ref, child) {
final value = ref.watch(helloWorldProvider);
return Text(value);
},
);
3. HookConsumerWidget
HookConsumerWidget是HookWidget和ConsumerWidget的结合。HookWidget的主要作用是在封装好的StateLessWidget,实现一些需要initState或者dispose回调的一些方法,比如AnimationController或者是做一些缓存。
而ConsumerWidget虽然是StatefulWidget,但是没办法回调initState和dispose,所以通过将HookConsumerWidget,就可以在使用Consumer的时候,实现一些需要initState和dispose的逻辑。
@override
Widget build(BuildContext context, WidgetRef ref) {
// 初始化一个变量,并且只会执行一次
final count = useState<T>(initValue);
// 改变变量的值,并且会刷新widget,相当于setState
count.value++;
useEffect({
// 做一些initState的操作
// 这里的执行只有Widget第一次build的时候才会执行
return method.call();// 这个方法在widget dispose的时候会执行。
})
// 一条语句可以实现animationController的初始化和自动dispose
final animationCtrl =
useAnimationController(duration: 300.milliseconds, initialValue: 0);
}
更多Hook用法参考:Hook的用法
4. HookConsumer
HookConsumer就是对HookConsumerWidget的进一步封装,对于一些不需要新建一个类去继承HookConsumerWidget的,那就直接用HookConsumer。就如同Consumer和ConsumerWidget之间的关系一样。
5. RiverpodConsumer
是内部自己实现的一个HookConsumer,传入listenable和builder可以实现provider的监听和rebuild。对于只需要监听单一变量时,使用RiverpodConsumer是比较方便的,但是如果一个widget需要监听多个变量,对这些变量做出对应的widget更新,那是不建议使用RiverpodConsumer进行多层嵌套的。因为RiverpodConsumer实际内部还是用Consumer实现的,而Consumer实际是一个StatefulWidget,所以RiverpodConsumer的嵌套实际上就是StatefulWidget的嵌套,这个是损耗性能并且是没必要的操作。还降低了代码的可读性。
6. WidgetRef的监听
WidgetRef中一共有4种调用provider的方式,分别是read, listen, watch和refresh,其中前3种比较常见和易用。read是获取provider的当前状态,如果后续provider发生改变了,那么获得的值并不会更新。并且,当provider是AutoDispose的,那么在read完这个provider之后,provider就会被dispose,即使当前页面没有销毁,provider也同样会被dispose。
而listen和watch是可以监听provider的变化。他们的区别是,listen是监听provider的变化,然后在回调函数中做对应的逻辑处理。而watch是监听provider的变化,然后让·ref对应的widget自动重建。
所以,当我们使用watch的时候应该尽可能的让底层的ref去做监听,来避免大量widget的重建。并且监听的时候尽量使用provider.select((value) => value.member)监听provider中的某个变量的变化。来避免provider其他不相关的变量发生变化引起不必要的重建。
另外,当我们监听provider中的集合时,如果是集合中的元素发生变化(增删改),通过provider.select((value) => value.collection)是没办法监听到的,此时只能ref.watch(provider)监听整个provider来获取集合元素的变化。除非是在集合元素发生变化后,重新对集合进行赋值,那就可以监听select。
对provider中的对象同理。如果是修改对象的某个成员变量,只监听该对象是无法获得该对象的某个成员变量的变化。需要监听该对象的成员变量。