在項(xiàng)目中,遇到了各種Consumer,剛開始的時(shí)候還不知道他們有什么區(qū)別,后面通過一些開發(fā)實(shí)踐逐漸摸清楚了他們之間的區(qū)別和使用場(chǎng)景。
首先,這些Consumer來源于riverpod這個(gè)庫(kù),主要是提升開發(fā)者對(duì)provider的使用,便于獲取provider,實(shí)現(xiàn)相關(guān)的狀態(tài)管理。
1. ConsumerWidget
是一個(gè)StatefulWidget,但是不需要我們?nèi)?shí)現(xiàn)相關(guān)的Widget和State。內(nèi)部已經(jīng)幫我們封裝實(shí)現(xiàn)好了。使用時(shí),只要繼承ConsumerWidget,實(shí)現(xiàn)Widget build(BuildContext context, WidgetRef ref);函數(shù)即可。然后通過ref獲取需要的provider
2. Consumer
是對(duì)ConsumerWidget的進(jìn)一步封裝。對(duì)于一些Widget我們不想去新建一個(gè)類來繼承ConsumerWidget,那我們可以使用Consumer來包裹Widget。
例子:
Consumer(
builder: (context, ref, child) {
final value = ref.watch(helloWorldProvider);
return Text(value);
},
);
3. HookConsumerWidget
HookConsumerWidget是HookWidget和ConsumerWidget的結(jié)合。HookWidget的主要作用是在封裝好的StateLessWidget,實(shí)現(xiàn)一些需要initState或者dispose回調(diào)的一些方法,比如AnimationController或者是做一些緩存。
而ConsumerWidget雖然是StatefulWidget,但是沒辦法回調(diào)initState和dispose,所以通過將HookConsumerWidget,就可以在使用Consumer的時(shí)候,實(shí)現(xiàn)一些需要initState和dispose的邏輯。
@override
Widget build(BuildContext context, WidgetRef ref) {
// 初始化一個(gè)變量,并且只會(huì)執(zhí)行一次
final count = useState<T>(initValue);
// 改變變量的值,并且會(huì)刷新widget,相當(dāng)于setState
count.value++;
useEffect({
// 做一些initState的操作
// 這里的執(zhí)行只有Widget第一次build的時(shí)候才會(huì)執(zhí)行
return method.call();// 這個(gè)方法在widget dispose的時(shí)候會(huì)執(zhí)行。
})
// 一條語句可以實(shí)現(xiàn)animationController的初始化和自動(dòng)dispose
final animationCtrl =
useAnimationController(duration: 300.milliseconds, initialValue: 0);
}
更多Hook用法參考:Hook的用法
4. HookConsumer
HookConsumer就是對(duì)HookConsumerWidget的進(jìn)一步封裝,對(duì)于一些不需要新建一個(gè)類去繼承HookConsumerWidget的,那就直接用HookConsumer。就如同Consumer和ConsumerWidget之間的關(guān)系一樣。
5. RiverpodConsumer
是內(nèi)部自己實(shí)現(xiàn)的一個(gè)HookConsumer,傳入listenable和builder可以實(shí)現(xiàn)provider的監(jiān)聽和rebuild。對(duì)于只需要監(jiān)聽單一變量時(shí),使用RiverpodConsumer是比較方便的,但是如果一個(gè)widget需要監(jiān)聽多個(gè)變量,對(duì)這些變量做出對(duì)應(yīng)的widget更新,那是不建議使用RiverpodConsumer進(jìn)行多層嵌套的。因?yàn)镽iverpodConsumer實(shí)際內(nèi)部還是用Consumer實(shí)現(xiàn)的,而Consumer實(shí)際是一個(gè)StatefulWidget,所以RiverpodConsumer的嵌套實(shí)際上就是StatefulWidget的嵌套,這個(gè)是損耗性能并且是沒必要的操作。還降低了代碼的可讀性。
6. WidgetRef的監(jiān)聽
WidgetRef中一共有4種調(diào)用provider的方式,分別是read, listen, watch和refresh,其中前3種比較常見和易用。read是獲取provider的當(dāng)前狀態(tài),如果后續(xù)provider發(fā)生改變了,那么獲得的值并不會(huì)更新。并且,當(dāng)provider是AutoDispose的,那么在read完這個(gè)provider之后,provider就會(huì)被dispose,即使當(dāng)前頁面沒有銷毀,provider也同樣會(huì)被dispose。
而listen和watch是可以監(jiān)聽provider的變化。他們的區(qū)別是,listen是監(jiān)聽provider的變化,然后在回調(diào)函數(shù)中做對(duì)應(yīng)的邏輯處理。而watch是監(jiān)聽provider的變化,然后讓·ref對(duì)應(yīng)的widget自動(dòng)重建。
所以,當(dāng)我們使用watch的時(shí)候應(yīng)該盡可能的讓底層的ref去做監(jiān)聽,來避免大量widget的重建。并且監(jiān)聽的時(shí)候盡量使用provider.select((value) => value.member)監(jiān)聽provider中的某個(gè)變量的變化。來避免provider其他不相關(guān)的變量發(fā)生變化引起不必要的重建。
另外,當(dāng)我們監(jiān)聽provider中的集合時(shí),如果是集合中的元素發(fā)生變化(增刪改),通過provider.select((value) => value.collection)是沒辦法監(jiān)聽到的,此時(shí)只能ref.watch(provider)監(jiān)聽整個(gè)provider來獲取集合元素的變化。除非是在集合元素發(fā)生變化后,重新對(duì)集合進(jìn)行賦值,那就可以監(jiān)聽select。
對(duì)provider中的對(duì)象同理。如果是修改對(duì)象的某個(gè)成員變量,只監(jiān)聽該對(duì)象是無法獲得該對(duì)象的某個(gè)成員變量的變化。需要監(jiān)聽該對(duì)象的成員變量。