前言
在Flutter開發中,狀態管理是一個永恆的話題。 通常的原則是:若是狀態是組件私有的,則應該由組件本身管理;若是狀態要跨組件共享,則該狀態應該由各個組件共同的父元素來管理。 對於組件私有的狀態管理很好理解,但對於跨組件共享的狀態,管理的方式就比較多了,如使用全局事件總線EventBus,它是一個觀察者模式的實現,經過它就能夠實現跨組件狀態同步:狀態持有方(發佈者)負責更新、發佈狀態,狀態使用方(觀察者)監聽狀態改變事件來執行一些操做。 可是觀察者模式來實現跨組件狀態共享有一些明顯的缺點:redux
- 必須顯示定義各類事件,很差管理;
- 訂閱者必須顯式註冊狀態改變回調,也必須在組件銷燬時手動去解綁回調以免內存泄漏;
是否還有更好的跨組件狀態管理方式了? 咱們知道,InheritedWidget能綁定與它依賴的子孫組件的依賴關係,而且當InheritedWidget數據發生變化時,能夠自動更新依賴的子孫組件。基於此,能夠將須要跨組件共享的狀態保存在InheritedWidget中,而後在子組件中引用InheritedWidget便可。Flutter社區的Provider包就是基於這個思想實現的。緩存
Provider
基於上面的思想,實現一個最小功能的Provider。app
定義一個保存共享數據的類
爲了通用性,使用泛型。less
class InheritedProvider<T> extends InheritedWidget{ InheritedProvider({@required this.data, Widget child}): super(child: child); // 共享狀態使用泛型 final T data; bool updateShouldNotify(InheritedProvider<T> old){ // 返回true,則每次更新都會調用依賴其的子孫節點的didChangeDependencies return true; } }
數據發生變化時如何重構InheritedProvider
存在兩個問題:ide
- 數據發生變化怎麼通知?
- 誰來從新構建InheritedProvider?
對於第一個問題,可使用以前介紹的eventBus來進行事件通知,可是爲了更貼近Flutter開發,使用Flutter中SDK中提供的ChangeNotifier類 ,它繼承自Listenable,也實現了一個Flutter風格的發佈者-訂閱者模式,能夠經過調用addListener()和removeListener()來添加、移除監聽器(訂閱者);經過調用notifyListeners() 能夠觸發全部監聽器回調。 對於第二個問題,將要共享的狀態放到一個Model類中,而後讓它繼承自ChangeNotifier,這樣當共享的狀態改變時,咱們只須要調用notifyListeners() 來通知訂閱者,而後由訂閱者來從新構建InheritedProvider。優化
// 該方法用於Dart獲取模板類型 Type _typeOf<T>() => T; class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget{ ChangeNotifierProvider({ Key key, this.data, this.child, }); final Widget child; final T data; // 定義一個便捷方法,方便子樹中的widget獲取共享數據 // static T of<T>(BuildContext context){ // final type = _typeOf<InheritedProvider<T>>(); // final provider = context.inheritFromWidgetOfExactType(type) as InheritedProvider<T>; // return provider.data; // } // 替換上面的便捷方法,按需求是否註冊依賴關係 static T of<T>(BuildContext context, {bool listen = true}){ final type = _typeOf<InheritedProvider<T>>(); final provider = listen ? context.inheritFromWidgetOfExactType(type) as InheritedProvider<T> : context.ancestorInheritedElementForWidgetOfExactType(type)?.widget as InheritedProvider<T>; return provider.data; } _ChangeNotifierProviderState<T> createState() => _ChangeNotifierProviderState<T>(); } // _ChangeNotifierProviderState類的主要做用就是監聽到共享狀態(model)改變時從新構建Widget樹。 // 注意,在_ChangeNotifierProviderState類中調用setState()方法,widget.child始終是同一個, // 因此執行build時,InheritedProvider的child引用的始終是同一個子widget, // 因此widget.child並不會從新build,這也就至關於對child進行了緩存!固然若是ChangeNotifierProvider父級Widget從新build時,則其傳入的child便有可能會發生變化。 class _ChangeNotifierProviderState<T extends ChangeNotifier> extends State<ChangeNotifierProvider<T>>{ void update(){ // 若是數據發生變化(model類調用了notifyListeners),從新構建InheritedProvider setState(() => {}); } @override void didUpdateWidget(ChangeNotifierProvider<T> oldWidget){ // 當Provider更新時,若是新舊數據不相等,則解綁舊數據監聽,同時添加新數據監聽 if(widget.data != oldWidget.data){ oldWidget.data.removeListener(update); widget.data.addListener(update); } super.didUpdateWidget(oldWidget); } @override void initState(){ // 給model添加監聽器 widget.data.addListener(update); super.initState(); } @override void dispose(){ // 移除model的監聽器 widget.data.removeListener(update); super.dispose(); } @override Widget build(BuildContext context){ return InheritedProvider<T>( data: widget.data, child: widget.child, ); } }
能夠看到_ChangeNotifierProviderState類的主要做用就是監聽到共享狀態(model)改變時從新構建Widget樹。注意,在_ChangeNotifierProviderState類中調用setState()方法,widget.child始終是同一個,因此執行build時,InheritedProvider的child引用的始終是同一個子widget,因此widget.child並不會從新build,這也就至關於對child進行了緩存!固然若是ChangeNotifierProvider父級Widget從新build時,則其傳入的child便有可能會發生變化。ui
代碼示例
// 跨組件狀態共享(Provider) // 一個通用的InheritedWidget,保存任須要跨組件共享的狀態 import 'dart:collection'; import 'package:flutter/material.dart'; class InheritedProvider<T> extends InheritedWidget{ InheritedProvider({@required this.data, Widget child}): super(child: child); // 共享狀態使用泛型 final T data; bool updateShouldNotify(InheritedProvider<T> old){ // 返回true,則每次更新都會調用依賴其的子孫節點的didChangeDependencies return true; } } // 該方法用於Dart獲取模板類型 Type _typeOf<T>() => T; class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget{ ChangeNotifierProvider({ Key key, this.data, this.child, }); final Widget child; final T data; // 定義一個便捷方法,方便子樹中的widget獲取共享數據 // static T of<T>(BuildContext context){ // final type = _typeOf<InheritedProvider<T>>(); // final provider = context.inheritFromWidgetOfExactType(type) as InheritedProvider<T>; // return provider.data; // } // 替換上面的便捷方法,按需求是否註冊依賴關係 static T of<T>(BuildContext context, {bool listen = true}){ final type = _typeOf<InheritedProvider<T>>(); final provider = listen ? context.inheritFromWidgetOfExactType(type) as InheritedProvider<T> : context.ancestorInheritedElementForWidgetOfExactType(type)?.widget as InheritedProvider<T>; return provider.data; } _ChangeNotifierProviderState<T> createState() => _ChangeNotifierProviderState<T>(); } // _ChangeNotifierProviderState類的主要做用就是監聽到共享狀態(model)改變時從新構建Widget樹。 // 注意,在_ChangeNotifierProviderState類中調用setState()方法,widget.child始終是同一個, // 因此執行build時,InheritedProvider的child引用的始終是同一個子widget, // 因此widget.child並不會從新build,這也就至關於對child進行了緩存!固然若是ChangeNotifierProvider父級Widget從新build時,則其傳入的child便有可能會發生變化。 class _ChangeNotifierProviderState<T extends ChangeNotifier> extends State<ChangeNotifierProvider<T>>{ void update(){ // 若是數據發生變化(model類調用了notifyListeners),從新構建InheritedProvider setState(() => {}); } @override void didUpdateWidget(ChangeNotifierProvider<T> oldWidget){ // 當Provider更新時,若是新舊數據不相等,則解綁舊數據監聽,同時添加新數據監聽 if(widget.data != oldWidget.data){ oldWidget.data.removeListener(update); widget.data.addListener(update); } super.didUpdateWidget(oldWidget); } @override void initState(){ // 給model添加監聽器 widget.data.addListener(update); super.initState(); } @override void dispose(){ // 移除model的監聽器 widget.data.removeListener(update); super.dispose(); } @override Widget build(BuildContext context){ return InheritedProvider<T>( data: widget.data, child: widget.child, ); } } // 購物車示例:實現一個顯示購物車中全部商品總價的功能 // 用於表示商品信息 class Item{ // 商品單價 double price; // 商品份數 int count; Item(this.price, this.count); } // 保存購物車內商品數據,跨組件共享 class CartModel extends ChangeNotifier{ // 用於保存購物車中商品列表 final List<Item> _items = []; // 禁止改變購物車裏的商品信息 UnmodifiableListView<Item> get items => UnmodifiableListView(_items); // 購物車中商品的總價 double get totalPrice => _items.fold(0, (value, item) => value + item.count * item.price); // 將[item]添加到購物車,這是惟一一種能從外部改變購物車的方法 void add(Item item) { _items.add(item); // 通知監聽者(訂閱者),從新構建InheritedProvider,更新狀態 notifyListeners(); } } // 優化 // 一個便捷類,封裝一個Consumer的Widget class Consumer<T> extends StatelessWidget{ final Widget child; final Widget Function(BuildContext context, T value) builder; Consumer({ Key key, @required this.builder, this.child, }): assert(builder != null), super(key: key); Widget build(BuildContext context){ return builder( context, // 自動獲取Model ChangeNotifierProvider.of<T>(context), ); } } // 頁面 class ProviderRoute extends StatefulWidget{ _ProviderRouteState createState() => _ProviderRouteState(); } class _ProviderRouteState extends State<ProviderRoute>{ @override Widget build(BuildContext context){ return Scaffold( appBar: AppBar( title: Text('跨組件狀態共享(Provider)'), ), body: Center( child: ChangeNotifierProvider<CartModel>( data: CartModel(), child: Builder(builder: (context){ return Column( children: <Widget>[ // Builder(builder: (context){ // var cart = ChangeNotifierProvider.of<CartModel>(context); // return Text("總價:${cart.totalPrice}"); // },), // 進行優化,替換上面Builder Consumer<CartModel>( builder: (context, cart) => Text("總價:${cart.totalPrice}"), ), Builder(builder: (context){ // 控制檯打印出這句,說明按鈕在每次點擊時其自身都會從新build! print("RaisedButton build"); return RaisedButton( child: Text("添加商品"), onPressed: (){ // 給購物車中添加商品,添加後總價會更新 // ChangeNotifierProvider.of<CartModel>(context).add(Item(20.0, 1)); // listen設爲false,不創建依賴關係,由於按鈕不須要每次從新build ChangeNotifierProvider.of<CartModel>(context, listen: false).add(Item(20.0, 1)); }, ); },) ], ); },), ), ), ); } }
代碼優化
兩個地方能夠進行代碼優化,詳細看代碼示例。this
總結
Provider原理圖
Model變化後會自動通知ChangeNotifierProvider(訂閱者),ChangeNotifierProvider內部會從新構建InheritedWidget,而依賴該InheritedWidget的子孫Widget就會更新。 能夠發現使用Provider,將會帶來以下收益:spa
- 業務代碼更關注數據了,只要更新Model,則UI會自動更新,而不用在狀態改變後再去手動調用setState()來顯式更新頁面。
- 數據改變的消息傳遞被屏蔽了,咱們無需手動去處理狀態改變事件的發佈和訂閱了,這一切都被封裝在Provider中了。這真的很棒,幫咱們省掉了大量的工做!
- 在大型複雜應用中,尤爲是須要全局共享的狀態很是多時,使用Provider將會大大簡化咱們的代碼邏輯,下降出錯的機率,提升開發效率。
其餘狀態管理包
Provider & Scoped Model、Redux、MobX、BLoC,這裏特別推薦阿里鹹魚團隊推出的開源項目:fish redux。code