書接上文
在Flutter狀態管理:Provider4 入門教程(一)中,我們對狀態管理以及Provider
有了初步的瞭解,也學習了ChangeNotifierProvider
以及Consumer
的使用,但由於時間有限,講到Consumer
就關賣了個關子,連廣告都忘打了,現在我們正式書接上文,從聊聊Consumer
開始。
怎麼還是Consumer
Consumer2-6
如果有細心的朋友在使用Consumer
時可能會發現,有個幾個神奇的類:
Consumer2
Consumer3
Consumer4
Consumer5
Consumer6
Consumer
成精了?
其實並不是哦。這其實是Consumer
大家族,一共六位成員,Consumer
後面的數字代表了Consumer
可接收的資料類數量。
天吶,原來是這個意思,那為什麼沒有Consumer1000
?親,這邊建議您去問作者吧,我怕被打死呢。所以說,如果當真有這麼個需求,還是自力更生吧。
說到這裡,我們再簡單地聊一下Consumer
。
整個Consumer
家族使用了Builder
模式,當Consumer
收到了更新就會通知builder
更新。而builder
實際上就是一個Function
,也可以說是一個lamda
了。以Conumer
為例,它的builder
被定義為Widget (BuildContext context, T model, Widget child)
,一共接收三個引數,其中的T
就是可以接收的資料型別,T
其實就是Consumer<T>
裡的泛型T
,所以說使用Consumer
時一定要提供泛型。以前文的程式碼為例,Consumer<MyChangeNotifier>
就是說這個Consumer
接收MyChangeNotifier
例項。以此類推,Consumer2<A,B>
可以接收A
和B
兩種型別的資料,從它的builder
的定義中就可以看出來:Widget Function(BuildContext context, A value, B value2, Widget child)
。
而builder
中的child
是用來構建那些與資料模型無關的部分,在多次執行builder
中,child
也不會進行重繪。
按道理來說,現在應該來說說Consumer3
了,但是考慮到他們其實都大同小異了,就不湊字數了,具體大家可以參看原始碼。
為什麼推薦使用Consumer
前文我說過:
Consumer本身沒有魔法,也沒有什麼花裡胡哨的實現。只不過是在一個新的控制元件中使用Provider.of,然後將這個控制元件的build方法委託給引數裡的builder。這個builder會被呼叫多次。就是這麼簡單。
但是我也說過Consumer
可以為我們提供效能上的優化,因為Consumer
可以為我們提供更細小的顆粒化重繪。
我們要知道當使用Provider.of
時,除非我們設定了listen:false
,否則只要Provider.of
中接收的資料發生了變化,與Provider.of
中的BuildeContext
相關的控制元件都會進行重新構建。這當然是我們期望的行為,但有時候這可能會引起不必要的過多的重繪。
我們看一個例子:
@override
Widget build(BuildContext context) {
return FooWidget(
child: BarWidget(
bar: Provider.of<Bar>(context),
),
);
}
複製程式碼
上面的程式碼中,只有BarWidget
依賴Provider.of
中返回的資料。但是當Bar
發生變化時,BarWidget
和FooWidget
都會進行重繪。
但理想情況中,應該只有BarWidget
進行重繪。一種解決方案就是使用Consumer
。
為了實現我們剛說的方法,我們將對用Consumer
對依賴Provider
的控制元件進行包裹。
@override
Widget build(BuildContext context) {
return FooWidget(
child: Consumer<Bar>(
builder: (_, bar, __) => BarWidget(bar: bar),
),
);
}
複製程式碼
現在,如果Bar
要進行更新,只有BarWidget
才會進行重繪。
但是如果FooWidget
依賴了一個provider
了怎麼辦? 比如說:
@override
Widget build(BuildContext context) {
return FooWidget(
foo: Provider.of<Foo>(context),
child: BarWidget(),
);
}
複製程式碼
還記得我上面說的child
嗎?對,就是它,上程式碼:
@override
Widget build(BuildContext context) {
return Consumer<Foo>(
builder: (_, foo, child) => FooWidget(foo: foo, child: child),
child: BarWidget(),
);
}
複製程式碼
這個例子中,BarWidget
是在builder
之外進行重繪的。然後BarWidget
例項作為最一個引數傳遞給給builder
。
這意味著builder
會被反覆呼叫時,Consumer
並不會建立BarWidget
新例項。這會讓Flutter
知道不必重新繪製BarWidget
。所以通過這麼樣的一個寫法,當Foo
有更新時,只有FooWidget
才會進行重繪。
有興趣的朋友,可以實現上述示例程式碼,然後在build()
中加幾個print
,然後驗證一下說的對不對。
Consumer小結
總得來說,Consumer
有兩個主要用處:
- 當我們的BuildContext中不存在指定的Provider時,Consumer允許我們從Provider中的獲取資料。
- 提升效能
所以我還是很建議使用Consumer
的,畢竟可以帶來效能提升。Flutter
誠然會我們做很多優化,但很多並不代表全部,效能的提升更多的是依靠我們自己的實現方式。
MultiProvider
我們之前一直在講一個頁面一個Provider
的情型,但實際上很多上了規模的應用,它可能不止一個Provider
,搞個幾十個provider
也是可能的,這時我們會想巢狀大法:
Provider<Something>(
create: (_) => Something(),
child: Provider<SomethingElse>(
create: (_) => SomethingElse(),
child: Provider<AnotherThing>(
create: (_) => AnotherThing(),
child: someWidget,
),
),
),
複製程式碼
一句以F開頭以U結尾的話是不是又要蹦出來?別急,作者很體貼地設計了MultiProvider
:
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
Provider<AnotherThing>(create: (_) => AnotherThing()),
],
child: someWidget,
)
複製程式碼
當然了,這兩段程式碼是完全等價的,所以MultiProvider
也沒有什麼黑魔法,只是不讓程式碼看起不那麼ugly
。
筆記:
Consumer
也可以在MultiProvider
使用。但Consumer
必須返回它在控制元件樹中建立的child
,也就是builder
中的builder
。
MultiProvider(
providers: [
Provider(create: (_) => Foo()),
Consumer<Foo>(
builder: (context, foo, child) =>
Provider.value(value: foo.bar, child: child),
)
],
);
複製程式碼
我有個問題
上面我們說到了MultiProvider
,忽然間腦袋靈光一現,有個問題想問:
- 可以在一個
BuildContext
中獲取具有相同資料型別的多個provider
嗎?
用程式碼說就是這樣式的:
Provider<String>(
create: (_) => '中國',
child: Provider<String>(
create: (_) => '大連',
child: ...,
),
),
複製程式碼
答案是否定的。當有多個具體相同資料型別的provider
時,一個控制元件只能獲取到一個:離他最近的。
所以嘞,我們必須明確地指出他們的型別:
Provider<Country>(
create: (_) => Country('中國'),
child: Provider<City>(
create: (_) => City('大連'),
child: ...,
),
),
複製程式碼
emmmm,看起來還不錯。
欲知後事請聽下回分解
作為Provider
系列的第二篇,內容依然很簡單,而我又要說時間有限了。
未完待續。。。 期待不期待你說了算。