provider 跨元件狀態管理
provider 跨元件狀態管理
Provider 包是由 Remi Rousselet 建立旨在儘可能快速地處理狀態。在 Provider 中,小部件會監聽狀態的變化,並在收到通知後立即更新。
因此,當有狀態改變時,而不是重建整個 widget 樹,只改變受影響的 widget,從而減少工作量並使應用程式執行得更快更流暢。
一、原理
Model變化後會自動通知ChangeNotifierProvider
(訂閱者),ChangeNotifierProvider
內部會重新構建InheritedWidget
,而依賴該InheritedWidget
的子孫Widget就會更新。
原有普遍方式:通過引數傳遞資料,並setState實時更新組建;
使用Provider益處:
-
業務程式碼更關注資料,只要更新Model,則UI會自動更新,而不用在狀態改變後再去手動呼叫
setState()
來顯式更新頁面。 -
資料改變的訊息傳遞被遮蔽了,我們無需手動去處理狀態改變事件的釋出和訂閱了,這一切都被封裝在Provider中了。這真的很棒,幫我們省掉了大量的工作!
-
在大型複雜應用中,尤其是需要全域性共享的狀態非常多時,使用Provider將會大大簡化我們的程式碼邏輯,降低出錯的概率,提高開發效率。
-
二、狀態管理方式
1、Provider 方式
provider 不需要被監聽,有的常量或者方法,根本不需要“牽一髮而動全身”,也就是說他們不會被要求隨著變動而變動,這樣的需求使用Provider;
最基本的狀態管理方式,以一個引數方式繫結和展示;
1) 繫結單條資料
Provider 可在需要的 Widget 處進行資料繫結;
基本單條資料繫結:
當我們確定繫結的資料型別時,建議繫結時新增資料型別,如:Provider<String>.value( value: '', child:);不確定型別Provider.value( value: '', child:);
Column( children: [ Provider<String>.value( value: 'Provider 基礎傳值', child: const BaseValueWidget()), ], )
2)獲取資料
class BaseValueWidget extends StatelessWidget { const BaseValueWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Text(Provider.of<String>(context)); } }
3)繫結多條資料
Column( children: [ /// 單條傳值 Provider<String>.value( value: 'Provider 基礎傳值', child: const BaseValueWidget()), /// 多條傳值 UserModel實體 //巢狀繫結 Provider<UserModel>.value( value: UserModel('巢狀繫結', 18), child: Provider<int>.value( value: 20, child: Provider<bool>.value( value: false, child: const NestingWidget()))), // 聚合方式 推薦 MultiProvider(providers: [ Provider<UserModel>.value(value: UserModel('聚合方式', 10)), Provider<int>.value(value: 11), Provider<bool>.value(value: false) ], child: const NestingWidget()), ], )
class UserModel { String name; int age; UserModel(this.name, this.age); }
獲取資料
class NestingWidget extends StatelessWidget { const NestingWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Text( 'Provider: ' '${Provider.of<int>(context)} | ${Provider.of<bool>(context)} | ${Provider.of<UserModel>(context).name} |${Provider.of<UserModel>(context).name = 'Hello World!'}', style: const TextStyle(color: Colors.redAccent)); } }Provider 繫結資料型別比較靈活,並非只是基本資料型別,這裡定義了一個 UserModel 類,可正常狀態管理;獲取 name 後重新設定 name 之後獲取的 UserModel 為最新的資料;
4)作用域
獲取繫結資料的範圍是在繫結資料的子 Widget 中;2、ChangeNotifierProvider 方式
ChangeNotifierProvider 它會隨著某些資料改變而被通知更新,也就是說,比如這個 Model 被用在多個 page,那麼當其中一處被改變時,他就應該告訴其他的地方,改更新了,這樣的需求就使用ChangeNotifierProvider;
通過呼叫 ChangeNotifier.notifyListeners 對 ChangeNotifier 進行監聽,將其公開給它的子 Widget 並重建依賴項;
1) 繫結資料
ChangeNotifierProvider 繫結資料有兩種方式:ChangeNotifierProvider({Key key, @required ValueBuilder builder, Widget child });通過構造器建立一個 ChangeNotifier,在 ChangeNotifierProvider 移除時自動處理;
ChangeNotifierProvider( create: (_) => PersonChangeNotifier('ChangeNotifier方式1', 11), child: const NotifierWidget(), ),
ChangeNotifierProvider.value({Key key, @required T notifier, Widget child }) 通過監聽通知給子 Widget 並重建依賴項;
ChangeNotifierProvider<PersonChangeNotifier>.value( value: PersonChangeNotifier('ChangeNotifier方式2', 1), child: const NotifierWidget(), )
class PersonChangeNotifier with ChangeNotifier { String name; int age; PersonChangeNotifier(this.name, this.age); updateName(String name) { this.name = name; notifyListeners(); } }
2) 獲取資料
獲取資料的方式與直接使用 Provider 相似;
Text(Provider.of<PersonChangeNotifier>(context).name);
相對於 Provider,ChangeNotifierProvider 方式更加靈活,可以通過重寫 get/set 方法來對狀態管理進行修改和使用;
3)更新資料
class NotifierWidget extends StatelessWidget { const NotifierWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Column( children: [ Text(Provider.of<PersonChangeNotifier>(context).name), InkWell( child: const Text('點選更新name'), onTap: () { context.read<PersonChangeNotifier>().updateName('name'); }, ), ], ); } }
3、ChangeNotifierProxyProvider
ChangeNotifierProxyProvider 它不僅要像ChangeNotifierProvider一樣,通知更新,還要協調 Model 與 Model 之間的更新,比如一個 ModelA 依賴另一個 ModelB,ModelB 更新,他就要讓依賴它的 ModelA 也隨之更新,這就是使用ChangeNotifierProxyProvider; https://pub.flutter-io.cn/documentation/provider/latest/provider/ChangeNotifierProxyProvider-class.htmlChangeNotifierProvider( create: (context) { return MyChangeNotifier( myModel: Provider.of<MyModel>(context, listen: false), ); }, child: ... )
三、有選擇地更新狀態
該Consumer控制元件只允許子控制元件,而不在 widget 樹影響其他部件重建,小部件進行更新。
我們通過Text
用 a 包裝兩個小部件Column
並builder
在Consumer
小部件公開的函式處返回它來實現這一點:
Consumer<UserDetailsProvider>( builder: (context, provider, child) { return Column( children: [ Text( 'Hi ' + provider.name, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), Text( 'You are ' + provider.age.toString() + ' years old', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w400, ), ), ], ); }, )