在項目中履恩,遇到了各種Consumer,剛開始的時候還不知道他們有什么區(qū)別呢蔫,后面通過一些開發(fā)實踐逐漸摸清楚了他們之間的區(qū)別和使用場景切心。
首先飒筑,這些Consumer來源于riverpod這個庫,主要是提升開發(fā)者對provider的使用绽昏,便于獲取provider协屡,實現(xiàn)相關(guān)的狀態(tài)管理。
1. ConsumerWidget
是一個StatefulWidget全谤,但是不需要我們?nèi)崿F(xiàn)相關(guān)的Widget和State肤晓。內(nèi)部已經(jīng)幫我們封裝實現(xiàn)好了。使用時认然,只要繼承ConsumerWidget补憾,實現(xiàn)Widget build(BuildContext context, WidgetRef ref);函數(shù)即可。然后通過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的結(jié)合子刮。HookWidget的主要作用是在封裝好的StateLessWidget威酒,實現(xiàn)一些需要initState或者dispose回調(diào)的一些方法,比如AnimationController或者是做一些緩存挺峡。
而ConsumerWidget雖然是StatefulWidget葵孤,但是沒辦法回調(diào)initState和dispose,所以通過將HookConsumerWidget橱赠,就可以在使用Consumer的時候尤仍,實現(xiàn)一些需要initState和dispose的邏輯。
@override
Widget build(BuildContext context, WidgetRef ref) {
// 初始化一個變量狭姨,并且只會執(zhí)行一次
final count = useState<T>(initValue);
// 改變變量的值宰啦,并且會刷新widget,相當于setState
count.value++;
useEffect({
// 做一些initState的操作
// 這里的執(zhí)行只有Widget第一次build的時候才會執(zhí)行
return method.call();// 這個方法在widget dispose的時候會執(zhí)行饼拍。
})
// 一條語句可以實現(xiàn)animationController的初始化和自動dispose
final animationCtrl =
useAnimationController(duration: 300.milliseconds, initialValue: 0);
}
更多Hook用法參考:Hook的用法
4. HookConsumer
HookConsumer就是對HookConsumerWidget的進一步封裝赡模,對于一些不需要新建一個類去繼承HookConsumerWidget的,那就直接用HookConsumer师抄。就如同Consumer和ConsumerWidget之間的關(guān)系一樣漓柑。
5. RiverpodConsumer
是內(nèi)部自己實現(xiàn)的一個HookConsumer,傳入listenable和builder可以實現(xiàn)provider的監(jiān)聽和rebuild叨吮。對于只需要監(jiān)聽單一變量時辆布,使用RiverpodConsumer是比較方便的,但是如果一個widget需要監(jiān)聽多個變量茶鉴,對這些變量做出對應的widget更新锋玲,那是不建議使用RiverpodConsumer進行多層嵌套的。因為RiverpodConsumer實際內(nèi)部還是用Consumer實現(xiàn)的涵叮,而Consumer實際是一個StatefulWidget惭蹂,所以RiverpodConsumer的嵌套實際上就是StatefulWidget的嵌套伞插,這個是損耗性能并且是沒必要的操作。還降低了代碼的可讀性剿干。
6. WidgetRef的監(jiān)聽
WidgetRef中一共有4種調(diào)用provider的方式蜂怎,分別是read, listen, watch和refresh穆刻,其中前3種比較常見和易用置尔。read是獲取provider的當前狀態(tài),如果后續(xù)provider發(fā)生改變了氢伟,那么獲得的值并不會更新榜轿。并且,當provider是AutoDispose的朵锣,那么在read完這個provider之后谬盐,provider就會被dispose,即使當前頁面沒有銷毀诚些,provider也同樣會被dispose飞傀。
而listen和watch是可以監(jiān)聽provider的變化。他們的區(qū)別是诬烹,listen是監(jiān)聽provider的變化砸烦,然后在回調(diào)函數(shù)中做對應的邏輯處理。而watch是監(jiān)聽provider的變化绞吁,然后讓·ref對應的widget自動重建幢痘。
所以,當我們使用watch的時候應該盡可能的讓底層的ref去做監(jiān)聽家破,來避免大量widget的重建颜说。并且監(jiān)聽的時候盡量使用provider.select((value) => value.member)監(jiān)聽provider中的某個變量的變化。來避免provider其他不相關(guān)的變量發(fā)生變化引起不必要的重建汰聋。
另外门粪,當我們監(jiān)聽provider中的集合時,如果是集合中的元素發(fā)生變化(增刪改)烹困,通過provider.select((value) => value.collection)是沒辦法監(jiān)聽到的玄妈,此時只能ref.watch(provider)監(jiān)聽整個provider來獲取集合元素的變化。除非是在集合元素發(fā)生變化后韭邓,重新對集合進行賦值措近,那就可以監(jiān)聽select。
對provider中的對象同理女淑。如果是修改對象的某個成員變量瞭郑,只監(jiān)聽該對象是無法獲得該對象的某個成員變量的變化。需要監(jiān)聽該對象的成員變量鸭你。