2019 Google I/O 大會,google就推出Provider,成爲官方推薦的狀態管理方式之一,Flutter 狀態管理一直是個很熱門的話題,並且狀態管理的庫也是超級多,這確實是咱們每個作Flutter開發難以免的一道坎,既然這麼重要,咱們如何去理解它,如何使用它,如何作到更好呢?接下來讓我告訴你答案html
該文章已經經歷了一週的迭代,預計還要一週左右,要作一個全面的分析,固然要每一個細節都要關注到,若是您以爲好,請不要吝嗇您的大拇指,順便點個贊哦,麼麼噠,若是有不對的地方提出來,一個地方一個紅包獎勵哦,愛大家。react
一張圖告訴你,我要講的主要內容。下面將圍繞這八個方面來說。七個理論,一個實踐。android
咱們知道最基本的程序是什麼:git
數據是程序的中心。數據結構和算法兩個概念間的邏輯關係貫穿了整個程序世界,首先兩者表現爲不可分割的關係。其實Flutter不就是一個程序嗎,那咱們面臨的最底層的問題仍是算法和數據結構,因此咱們推導出github
那狀態管理是什麼?我也用公式來表達一下,以下:算法
瞬間秒懂有沒有?來看一個代碼例子:數據庫
class ThemeBloc { final _themeStreamController = StreamController<AppTheme>(); get changeTheTheme => _themeStreamController.sink.add; get darkThemeIsEnabled => _themeStreamController.stream; dispose() { _themeStreamController.close(); } } final bloc = ThemeBloc(); class AppTheme { ThemeData themeData; AppTheme(this.themeData); } /// 綁定到UI StreamBuilder<AppTheme>( initialData: AppTheme.LIGHT_THEME, stream: bloc.darkThemeIsEnabled, builder: (context, AsyncSnapshot<AppTheme> snapshot) { return MaterialApp( title: 'Jetpack', theme: snapshot.data.themeData, home: PageHome(), routes: <String, WidgetBuilder>{ "/pageChatGroup": (context) => PageChatGroup(), "/LaoMeng": (context) => LaoMeng(), }, ); })
這樣一整套代碼的邏輯就是咱們所說的Flutter狀態管理,這樣解釋你們理解了嗎?再細說,算法就是咱們如何管理,數據結構就是數據狀態,狀態管理的本質仍是如何經過合理的算法管理數據,如何取,如何接收等,最終展現在UI上,經過UI的變動來體現狀態的管理邏輯。redux
這裏就須要明白一個事情,Flutter的不少優秀的設計都來源於React,對於react來講,同級組件之間的通訊尤其麻煩,或者是很是麻煩了,因此咱們把全部須要多個組件使用的state拿出來,整合到頂部容器,進行分發。狀態管理能夠實現組件通訊、跨組件數據儲存。推薦閱讀對 React 狀態管理的理解及方案對比,那麼對於Flutter來講呢?你知道Android、Ios等原生於Flutter最本質的區別嗎?來看一段代碼:api
//android TextView tv = TextView() tv.setText("text")
///flutter setState{ text = "text" }
從上面代碼咱們看出,Android的狀態變動是經過具體的組件直接賦值,若是頁面所有變動,你是否是須要每個都設置一遍呢?,而Flutter的變動就簡單粗暴,setState搞定,它背後的邏輯是從新build整個頁面,發現有變動,再將新的數據賦值,其實Android、Ios與flutter的本質的區別就是數據與視圖徹底分離,固然Android也出現了UI綁定框架,彷佛跟React、Flutter愈來愈像,因此這也在另外一方面凸顯出了,Flutter設計的先進性,沒有什麼創新,但更符合將來感,回過頭來,仔細想想,這樣設計有什麼弊端?安全
對了你猜對了:頁面如何刷新纔是Flutter的關鍵,作Android的同窗確定也面臨着一個問題,頁面的重繪致使的丟幀問題,爲了更好,咱們不少時候都選擇了局部刷新來優化對吧,Android、Ios已經很明確的告訴UI要刷新什麼更新什麼,而對於Flutter來講,這一點很不清晰,雖然Flutter也作了相似虛擬Dom優化重繪邏輯,但這些遠遠不夠的,如何合理的更新UI纔是最主要的,這個時候一大堆的狀態管理就出來了,固然狀態管理也不是僅僅爲了解決更新問題。
我再拋出一個問題,若是我有一個widget A,我想在另一個widget B中改變widget A的一個狀態,或者從網絡、數據庫取到數據,而後刷新它,怎麼作?咱們來模擬一下,來看代碼
糟糕的狀態管理代碼
class WidgetTest extends StatefulWidget { @override _WidgetTestState createState() => _WidgetTestState(); } class _WidgetTestState extends State<WidgetTest> { @override Widget build(BuildContext context) { return Container( child: Column( children: <Widget>[ WidgetA(), WidgetB() ], ), ); } } _WidgetAState _widgetAState; class WidgetA extends StatefulWidget { @override _WidgetAState createState() { _widgetAState = _WidgetAState(); return _widgetAState; } } class _WidgetAState extends State<WidgetA> { var title = ""; @override Widget build(BuildContext context) { return Container( child: Text(title), ); } } class WidgetB extends StatefulWidget { @override _WidgetBState createState() => _WidgetBState(); } class _WidgetBState extends State<WidgetB> { @override Widget build(BuildContext context) { return Container( child: RaisedButton( onPressed: () { _widgetAState.setState(() { _widgetAState.title = "WidgetB"; }); }, ), ); } }
WidgetTest頁面有兩個widget,分別是WidgetA、WidgetB,WidgetB經過RaisedButton的onPressed來改變WidgetA的Text,怎麼作到的呢,直接用WidgetA的_WidgetAState對象提供的setState函數來變動,沒什麼問題對吧,並且功能實現了,但你仔細思考一下,這有什麼問題呢?
如何變好呢
這就須要選擇一種合適的狀態管理方式。
狀態管理的目標
其實咱們作狀態管理,不只僅是由於它的特色,而爲了更好架構,不是嗎?
這些不牢牢是狀態管理的目的,也是咱們作一款優秀應用的基礎架構哦。
舉個例子,以下方的_index,這就是一個局部或者短暫狀態,只須要StatefulWidget處理便可完成
class MyHomepage extends StatefulWidget { @override _MyHomepageState createState() => _MyHomepageState(); } class _MyHomepageState extends State<MyHomepage> { int _index = 0; @override Widget build(BuildContext context) { return BottomNavigationBar( currentIndex: _index, onTap: (newIndex) { setState(() { _index = newIndex; }); }, // ... items ... ); } }
例如:
狀態分類官方定義
沒有明確的通用規則來區分特定變量是短暫狀態仍是應用程序狀態。有時,您必須將一個重構爲另外一個。例如,您將從一個明顯的短暫狀態開始,可是隨着您的應用程序功能的增加,可能須要將其移至應用程序狀態。 出於這個緣由,請使用下圖進行分類:
總之,任何Flutter應用程序中都有兩種概念性的狀態類型。臨時狀態可使用State和setState()來實現,而且一般是單個窗口小部件的本地狀態。剩下的就是您的應用狀態。兩種類型在任何Flutter應用程序中都有本身的位置,二者之間的劃分取決於您本身的喜愛和應用程序的複雜性
沒有最好的管理方式,只有最合適的管理方式
底層邏輯我想告訴你的是,Flutter中目前有哪些能夠作到狀態管理,有什麼缺點,適合作什麼不適合作什麼,只有你徹底明白底層邏輯,纔不會畏懼複雜的邏輯,即便是複雜的邏輯,你也能選擇合理的方式去管理狀態。
StatefulWidget、StreamBuilder狀態管理方式
專門負責Widget樹中數據共享的功能型Widget,如Provider、scoped_model就是基於它開發
與InheritedWidget正好相反,InheritedWidget是從上往下傳遞數據,Notification是從下往上,但二者都在本身的Widget樹中傳遞,沒法跨越樹傳遞。
數據流 如Bloc、flutter_redux、fish_redux等也都基於它來作實現
爲何列這些東西?由於如今大部分流行的狀態管理都離不開它們。理解它們比理解那些吹本身牛逼的框架要好的多。請關注底層邏輯,這樣你才能遊刃有餘。下面咱們一個個分析一下:
State 是咱們經常使用並且使用最頻繁的一個狀態管理類,它必須結合StatefulWidget一塊兒使用,StreamBuilder繼承自StatefulWidget,一樣是經過setState來管理狀態
舉個例子來看下:
class TapboxA extends StatefulWidget { TapboxA({Key key}) : super(key: key); @override _TapboxAState createState() => _TapboxAState(); } class _TapboxAState extends State<TapboxA> { bool _active = false; void _handleTap() { setState(() { _active = !_active; }); } Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Container( child: Center( child: Text( _active ? 'Active' : 'Inactive', style: TextStyle(fontSize: 32.0, color: Colors.white), ), ), width: 200.0, height: 200.0, decoration: BoxDecoration( color: _active ? Colors.lightGreen[700] : Colors.grey[600], ), ), ); } }
引用官方的例子,這裏_active狀態就是經過State提供的setState函數來實現的
爲何會讓State去管理狀態,而不是Widget自己呢?Flutter設計時讓Widget自己是不變的,相似固定的配置信息,那麼就須要一個角色來控制它,State就出現了,但State的任何更改都會強制整個Widget從新構建,固然你也能夠覆蓋必要方法本身控制邏輯。
再看個例子:
class ParentWidget extends StatefulWidget { @override _ParentWidgetState createState() => _ParentWidgetState(); } class _ParentWidgetState extends State<ParentWidget> { bool _active = false; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return Container( child: TapboxB( active: _active, onChanged: _handleTapboxChanged, ), ); } } //------------------------- TapboxB ---------------------------------- class TapboxB extends StatelessWidget { TapboxB({Key key, this.active: false, @required this.onChanged}) : super(key: key); final bool active; final ValueChanged<bool> onChanged; void _handleTap() { onChanged(!active); } Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Container( child: Center( child: Text( active ? 'Active' : 'Inactive', style: TextStyle(fontSize: 32.0, color: Colors.white), ), ), width: 200.0, height: 200.0, decoration: BoxDecoration( color: active ? Colors.lightGreen[700] : Colors.grey[600], ), ), ); } }
從這裏你看出什麼?對了,父組件能夠經過setState來刷新子Widget的狀態變化,因此得出以下觀點
注意
setState是整個Widget從新構建(並且子Widget也會跟着銷燬重建),這個點也是爲何不推薦你大量使用StatefulWidget的緣由。若是頁面足夠複雜,就會致使嚴重的性能損耗。如何優化呢?建議使用StreamBuilder,它原理上也是State,但它作到了子Widget的局部刷新,不會致使整個頁面的重建,是否是就好不少了呢?
從上面的代碼咱們分析一下它的缺點
setState是State的函數,通常咱們會將State的子類設置爲私有,因此沒法作到讓別的組件調用State的setState函數來刷新
隨着頁面狀態的增多,你可能在調用setState的地方會愈來愈多,不能統一管理
好比數據庫的數據取出來setState到Ui上,這樣編寫代碼,致使狀態和UI耦合在一塊兒,不利於測試,不利於複用。
固然反過來說,不是由於它有缺點咱們就不使用了,咱們追求的簡單高效,簡單實現,高效運行,當複雜到須要更好的管理的時候再重構。一個基本原則就是,狀態是否須要跨組件使用,若是須要那就用別的辦法管理狀態而不是State管理。
InheritedWidget是一個無私的Widget,它能夠把本身的狀態數據,無私的交給全部的子Widget,全部的子Widget能夠無條件的繼承它的狀態。就這麼一個東西。有了State咱們爲何還須要它呢?咱們已經知道,State是能夠更新直接子Widget的狀態,但若是是子Widget的子Widget呢,因此說InheritedWidget的存在,一是爲了更簡單的獲取狀態,二是你們都共享這個狀態,舉個例子
class InheritedWidgetDemo extends InheritedWidget { final int accountId; InheritedWidgetDemo(this.accountId, {Key key, Widget child}) : super(key: key, child: child); @override bool updateShouldNotify(InheritedWidgetDemo old) => accountId != old.accountId; static InheritedWidgetDemo of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<InheritedWidgetDemo>(); } } class MyPage extends StatelessWidget { final int accountId; MyPage(this.accountId); Widget build(BuildContext context) { return new InheritedWidgetDemo( accountId, child: const MyWidget(), ); } } class MyWidget extends StatelessWidget { const MyWidget(); Widget build(BuildContext context) { return MyOtherWidget(); } } class MyOtherWidget extends StatelessWidget { @override Widget build(BuildContext context) { final myInheritedWidget = InheritedWidgetDemo.of(context); print(myInheritedWidget.accountId); } }
有的人想了,InheritedWidget這麼好用,那我把整個App的狀態都存進來怎麼樣?相似這樣
class AppContext { int teamId; String teamName; int studentId; String studentName; int classId; ... }
其實這樣很差,咱們不光是要作技術上的組件化,更要關注的是業務,對業務的充分理解並實現模塊化分工,在使用InheritedWidget時候特別是要注意這一點,更推薦你使用該方案:
class TeamContext { int teamId; String teamName; } class StudentContext { int studentId; String studentName; } class ClassContext { int classId; ... }
注意
它的數據是隻讀的,雖然很無私,但子widget不能修改,那麼如何修改呢?
舉個例子:
class Item { String reference; Item(this.reference); } class _MyInherited extends InheritedWidget { _MyInherited({ Key key, @required Widget child, @required this.data, }) : super(key: key, child: child); final MyInheritedWidgetState data; @override bool updateShouldNotify(_MyInherited oldWidget) { return true; } } class MyInheritedWidget extends StatefulWidget { MyInheritedWidget({ Key key, this.child, }): super(key: key); final Widget child; @override MyInheritedWidgetState createState() => new MyInheritedWidgetState(); static MyInheritedWidgetState of(BuildContext context){ return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data; } } class MyInheritedWidgetState extends State<MyInheritedWidget>{ /// List of Items List<Item> _items = <Item>[]; /// Getter (number of items) int get itemsCount => _items.length; /// Helper method to add an Item void addItem(String reference){ setState((){ _items.add(new Item(reference)); }); } @override Widget build(BuildContext context){ return new _MyInherited( data: this, child: widget.child, ); } } class MyTree extends StatefulWidget { @override _MyTreeState createState() => new _MyTreeState(); } class _MyTreeState extends State<MyTree> { @override Widget build(BuildContext context) { return new MyInheritedWidget( child: new Scaffold( appBar: new AppBar( title: new Text('Title'), ), body: new Column( children: <Widget>[ new WidgetA(), new Container( child: new Row( children: <Widget>[ new Icon(Icons.shopping_cart), new WidgetB(), new WidgetC(), ], ), ), ], ), ), ); } } class WidgetA extends StatelessWidget { @override Widget build(BuildContext context) { final MyInheritedWidgetState state = MyInheritedWidget.of(context); return new Container( child: new RaisedButton( child: new Text('Add Item'), onPressed: () { state.addItem('new item'); }, ), ); } } class WidgetB extends StatelessWidget { @override Widget build(BuildContext context) { final MyInheritedWidgetState state = MyInheritedWidget.of(context); return new Text('${state.itemsCount}'); } } class WidgetC extends StatelessWidget { @override Widget build(BuildContext context) { return new Text('I am Widget C'); } }
該例子引用自widget-state-context-inheritedwidget/歡迎閱讀學習哦
看了一下日誌輸出如圖:
有沒有發現一個問題?當MyInheritedWidgetState.addItem,致使setState被調用,而後就觸發了WidgetA、WidgetB的build的方法,而WidgetA根本不須要從新build,這不是浪費嗎?那麼咱們如何優化呢?
static MyInheritedWidgetState of([BuildContext context, bool rebuild = true]){ return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited : context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data; }
經過抽象rebuild屬性來控制是否須要從新build
final MyInheritedWidgetState state = MyInheritedWidget.of(context,false);
而後用的時候加以參數控制,改完代碼,再看下日誌:
看,已經生效了。你如今是否是對InheritedWidget有了更清晰的認識了呢?但說到它就不得不提InheritedModel,它是InheritedWidget的子類,InheritedModel能夠作到部分數據改變的時候纔會重建,你能夠修改上面例子
class _MyInheritedWidget extends InheritedModel { static MyInheritedWidgetState of(BuildContext context, String aspect) { return InheritedModel.inheritFrom<_MyInheritedWidget>(context, aspect: aspect).data; } @override bool updateShouldNotifyDependent(_MyInheritedWidget old, Set aspects) { return aspects.contains('true'); } }
調用修改成:
///不容許從新build final MyInheritedWidgetState state = MyInheritedWidget.of(context,"false"); ///容許從新build final MyInheritedWidgetState state = MyInheritedWidget.of(context,"true");
推薦閱讀
inheritedmodel-vs-inheritedwidget
https://juju.one/inheritedwidget-inheritedmodel/
widget-state-context-inheritedwidget/
經過上面的分析,咱們來看下它的缺點
通過一系列的舉例和驗證,你也基本的掌握了InheritedWidget了吧,這個組件特別適合在同一樹型Widget中,抽象出公有狀態,每個子Widget或者孫Widget均可以獲取該狀態,咱們還能夠經過手段控制rebuild的粒度來優化重繪邏輯,但它更適合從上往下傳遞,若是是從下往上傳遞,咱們如何作到呢?請往下看,立刻給你解答
它是Flutter中跨層數據共享的一種機制,注意,它不是widget,它提供了dispatch方法,來讓咱們沿着context對應的Element節點向上逐層發送通知
具個簡單例子看下
class TestNotification extends Notification { final int test; TestNotification(this.test); } var a = 0; // ignore: must_be_immutable class WidgetNotification extends StatelessWidget { final String btnText; WidgetNotification({Key key, this.btnText}) : super(key: key); @override Widget build(BuildContext context) { return Container( child: RaisedButton( child: Text(btnText), onPressed: () { var b = ++a; debugPrint(b.toString()); TestNotification(b).dispatch(context); }, ), ); } } class WidgetListener extends StatefulWidget { @override _WidgetListenerState createState() => _WidgetListenerState(); } class _WidgetListenerState extends State<WidgetListener> { int _test = 1; @override Widget build(BuildContext context) { return Container( child: Column( children: <Widget>[ NotificationListener<TestNotification>( child: Column( children: <Widget>[ Text("監聽$_test"), WidgetNotification(btnText: "子Widget",) ], ), onNotification: (TestNotification notification) { setState(() { _test = notification.test; }); return true; }, ), WidgetNotification(btnText: "非子Widget",) ], ), ); } }
因此說在使用Notification的時候要注意,若是遇到沒法收到通知的狀況,考慮是不是Notification 未在NotificationListener的內部發出通知,這個必定要注意。
一樣的思路,我想看下Notification是如何刷新Ui的
在代碼里加入了跟通知可有可無的WidgetC
這麼看來,你覺得是Notification致使的嗎?我把這個註釋掉,如圖
再運行看下,連續點擊了八次
原來是State的緣由,那麼這種狀況咱們如何優化呢?這就用到了Stream了,請接着往下繼續看哦。
推薦閱讀
flutter-notifications-bubble-up-and-values-go-down
使用起來很簡單,但在刷新UI方面須要注意,若是頁面複雜度很高,致使可有可無的組件跟着刷新,得不償失,還須要另找蹊徑,躲開這些坑,下面我來介紹如何完美躲閃,重磅來襲Stream。
它實際上是純Dart的實現,跟Flutter沒什麼關係,扯上關係的就是用StreamBuilder來構建一個Stream通道的Widget,像知名的rxdart、BloC、flutter_redux全都用到了Stream的api。因此學習它纔是咱們掌握狀態管理的一個關鍵
推薦閱讀
我本身寫的StreamBuilder源碼分析
大神寫的Stream全面分析
缺點偏偏是它的優勢,保證了足夠靈活,你更可基於它作一個好的設計,知足當下業務的設計。
經過對State、InheritedWidget、Notification、Stream的學習,你是否是以爲,Flutter的狀態管理也就這些了呢?不必定哈,我也在不斷的學習,若是碰到新的技術,繼續分享給大家哦。難道這就完了嗎?沒有,其實咱們只是學了第一步,是什麼,如何用,尚未討論怎麼用好呢?須要什麼標準嗎,固然有,下面經過個人項目實戰經驗來提出一個基本原則,超過這個原則你就是在破壞平衡,請往下看。
這個原則來源於,Flutter的性能優化,局部刷新確定比全局刷新要好不少,那麼咱們在管理狀態的同時,也要考慮該狀態究竟是局部仍是全局,從而編寫正確的邏輯。
用「_」私有化狀態,由於當開發人員衆多,當別人看到你的變量的時候,第一反應可能不是找你提供的方法,而是直接對變量操做,那就有可能出現想不到的後果,若是他只能調用你提供的方法,那他就要遵循你方法的邏輯,避免數據被處理錯誤。
不少時候頁面的重建都會調用build函數,也就是說,在一個生命週期內,build函數是屢次被調用的,因此你就要考慮數據的初始化或者刷新怎麼樣才能合理。
經過了解它們的弊端來規避一些風險,綜合考慮,選框架不易,且行且珍惜。
全部的框架都有侵入性,你贊成嗎?不一樣意請左轉,前面有個坑,你能夠跳過去。目前侵入性比較高的表明ScopedModel,爲啥?由於它是用extend實現的,須要繼承實現的基本不是什麼好實現,你贊成嗎?同上。
擴展性就不用說了,若是你選擇的框架只能使用它提供的幾個入口,那麼請你放棄使用它。高性能也是很重要的,這個須要明白它的原理,看它到底如何作的管理。安全性也很重要,看他數據管理通道是否安全穩定。駕馭性,你說你都不理解你就敢用,出了問題找誰?若是駕馭不了也不要用。易用性你們應該都明白,若是用它一個框架須要N多配置,N多實現,放棄吧,不合適。簡單纔是硬道理。
範圍性
這個特色是flutter中比較明顯的,框架選型必定要考慮框架的適用範圍,究竟是適合作局部管理,仍是適合全局管理,要作一個實際的考量。
若是是初期,建議多使用Stream、State、Notification來自行處理,順便學習源碼,多理解,多實踐。有架構能力的就能夠着手封裝了,提供更簡單的使用方式
若是是後期,固然也是在前面的基礎之上,再去考慮使用Provider、redux等複雜的框架,原則上要吃透源碼,不然不建議使用。
你覺得使用框架就能萬事大吉了?性能優化是一個不變的話題,包括Provider在內的,若是你使用不當,照樣出現頁面的性能損耗嚴重,因此你又回到了爲啥會這樣,請你學習上面的底層邏輯,謝謝🙏
經過這期分享,你是否是對Flutter的狀態管理有了一個從新的認識呢?若是對你有幫住,請點一下下面的贊哦。謝謝🙏。