Flutter State Management狀態管理全面分析

前言

2019 Google I/O 大會,google就推出Provider,成爲官方推薦的狀態管理方式之一,Flutter 狀態管理一直是個很熱門的話題,並且狀態管理的庫也是超級多,這確實是咱們每個作Flutter開發難以免的一道坎,既然這麼重要,咱們如何去理解它,如何使用它,如何作到更好呢?接下來讓我告訴你答案html

囉嗦幾句

該文章已經經歷了一週的迭代,預計還要一週左右,要作一個全面的分析,固然要每一個細節都要關注到,若是您以爲好,請不要吝嗇您的大拇指,順便點個贊哦,麼麼噠,若是有不對的地方提出來,一個地方一個紅包獎勵哦,愛大家。react

主要內容

一張圖告訴你,我要講的主要內容。下面將圍繞這八個方面來說。七個理論,一個實踐。android

  • 狀態管理是什麼
  • 爲何須要狀態管理
  • 狀態管理基本分類
  • 狀態管理的底層邏輯
  • 狀態管理的使用原則
  • 使用成熟狀態管理庫的弊端
  • 選擇狀態管理庫的原則
  • Provider 深刻分析(學以至用)

狀態管理是什麼

咱們知道最基本的程序是什麼:git

  • 程序=算法+數據結構

數據是程序的中心。數據結構和算法兩個概念間的邏輯關係貫穿了整個程序世界,首先兩者表現爲不可分割的關係。其實Flutter不就是一個程序嗎,那咱們面臨的最底層的問題仍是算法和數據結構,因此咱們推導出github

  • Flutter=算法+數據結構

那狀態管理是什麼?我也用公式來表達一下,以下:算法

  • Flutter狀態管理=算法+數據結構+UI綁定

瞬間秒懂有沒有?來看一個代碼例子:數據庫

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(),
            },
          );
        })
  • AppTheme 是數據結構
  • changeTheTheme 是算法
  • StreamBuilder 是綁定UI

這樣一整套代碼的邏輯就是咱們所說的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函數來變動,沒什麼問題對吧,並且功能實現了,但你仔細思考一下,這有什麼問題呢?

  • _WidgetAState 被全局化,並且它全部狀態被暴漏出去,若是_WidgetAState有十個狀態,只有一個想讓別人變動,惋惜已經晚了, 你加'_'也不行,組件的隱私全沒了
  • 耦合變高,WidgetB有_WidgetAState的強關聯,咱們編碼追求的解偶,在這裏徹底被忽視了
  • 性能變差,爲何這麼說?由於每次_widgetAState.setState都會致使整個頁面甚至子Widget的從新build,若是_widgetAState裏面有成千上百的狀態,性能確定差到極點
  • 不可測,程序變得難以測試

如何變好呢
這就須要選擇一種合適的狀態管理方式。

狀態管理的目標
其實咱們作狀態管理,不只僅是由於它的特色,而爲了更好架構,不是嗎?

  • 代碼要井井有條,易維護,易閱讀
  • 可擴展,易維護,能夠動態替換UI而不影響算法邏輯
  • 安全可靠,保持數據的穩定伸縮
  • 性能佳,局部優化

這些不牢牢是狀態管理的目的,也是咱們作一款優秀應用的基礎架構哦。

基本分類

  • 局部管理 官方也稱 Ephemeral state,意思是短暫的狀態,這種狀態根本不須要作全局處理

舉個例子,以下方的_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 ...
    );
  }
}
  • 全局管理 官方稱 App state,即應用狀態,非短暫狀態,您要在應用程序的許多部分之間共享,以及但願在用戶會話之間保持的狀態,就是咱們所說的應用程序狀態(有時也稱爲共享狀態)

例如:

  • 用戶偏好
  • 登陸信息
  • 購物車
  • 新聞閱讀狀態

狀態分類官方定義

沒有明確的通用規則來區分特定變量是短暫狀態仍是應用程序狀態。有時,您必須將一個重構爲另外一個。例如,您將從一個明顯的短暫狀態開始,可是隨着您的應用程序功能的增加,可能須要將其移至應用程序狀態。 出於這個緣由,請使用下圖進行分類:

總之,任何Flutter應用程序中都有兩種概念性的狀態類型。臨時狀態可使用State和setState()來實現,而且一般是單個窗口小部件的本地狀態。剩下的就是您的應用狀態。兩種類型在任何Flutter應用程序中都有本身的位置,二者之間的劃分取決於您本身的喜愛和應用程序的複雜性


沒有最好的管理方式,只有最合適的管理方式

底層邏輯

底層邏輯我想告訴你的是,Flutter中目前有哪些能夠作到狀態管理,有什麼缺點,適合作什麼不適合作什麼,只有你徹底明白底層邏輯,纔不會畏懼複雜的邏輯,即便是複雜的邏輯,你也能選擇合理的方式去管理狀態。

  • State

StatefulWidget、StreamBuilder狀態管理方式

  • InheritedWidget

專門負責Widget樹中數據共享的功能型Widget,如Provider、scoped_model就是基於它開發

  • Notification

與InheritedWidget正好相反,InheritedWidget是從上往下傳遞數據,Notification是從下往上,但二者都在本身的Widget樹中傳遞,沒法跨越樹傳遞。

  • Stream

數據流 如Bloc、flutter_redux、fish_redux等也都基於它來作實現
爲何列這些東西?由於如今大部分流行的狀態管理都離不開它們。理解它們比理解那些吹本身牛逼的框架要好的多。請關注底層邏輯,這樣你才能遊刃有餘。下面咱們一個個分析一下:

State

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的局部刷新,不會致使整個頁面的重建,是否是就好不少了呢?

State缺點

從上面的代碼咱們分析一下它的缺點

  • 沒法作到跨組件共享數據(這個跨是無關聯的,若是是直接的父子關係,咱們不認爲是跨組件)

setState是State的函數,通常咱們會將State的子類設置爲私有,因此沒法作到讓別的組件調用State的setState函數來刷新

  • setState會成爲維護的難點,由於啥哪哪都是。

隨着頁面狀態的增多,你可能在調用setState的地方會愈來愈多,不能統一管理

  • 處理數據邏輯和視圖混合在一塊兒,違反代碼設計原則

好比數據庫的數據取出來setState到Ui上,這樣編寫代碼,致使狀態和UI耦合在一塊兒,不利於測試,不利於複用。

State小結

固然反過來說,不是由於它有缺點咱們就不使用了,咱們追求的簡單高效,簡單實現,高效運行,當複雜到須要更好的管理的時候再重構。一個基本原則就是,狀態是否須要跨組件使用,若是須要那就用別的辦法管理狀態而不是State管理。

InheritedWidget

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);
  }

}
  • InheritedWidgetDemo共享狀態accountId給了MyOtherWidget,而MyOtherWidget是MyWidget的子Widget,這就是InheritedWidget的功效,它能夠作到跨組件共享狀態。
  • const MyWidget() 表示該Widget是常量,不會由於頁面的刷新致使從新build,這就是優化的細節,這裏想一下,若是你用State實現,不是就須要它setState才能實現MyOtherWidget的從新build,這樣作的壞處就是致使整個UI的刷新。
  • updateShouldNotify 它也是一個優化點,在你橫屏變豎屏的同時,致使整個UI從新build,可因爲updateShouldNotify的判斷,系統將不會從新build MyOtherWidget,也是一種佈局優化。
  • 子樹中的組件經過InheritedWidgetDemo.of(context)訪問共享狀態。

有的人想了,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/歡迎閱讀學習哦

  • _MyInherited是InheritedWidget,每次咱們經過單擊「 Widget A」的按鈕添加元素時都會從新建立
  • MyInheritedWidget是一個狀態爲包含元素列表的窗口小部件。可經過(BuildContext上下文)的靜態MyInheritedWidgetState訪問此狀態
  • MyInheritedWidgetState公開一個getter(itemsCount)和一個方法(addItem),以便子控件樹的一部分的控件可使用它們
  • 每次咱們向State添加元素時,都會從新構建MyInheritedWidgetState
  • MyTree類僅構建一個小部件樹,將MyInheritedWidget做爲該樹的父級
  • WidgetA是一個簡單的RaisedButton,按下該按鈕時,會調用最近的MyInheritedWidget的addItem方法。
  • WidgetB是一個簡單的Text,它顯示在最接近的 MyInheritedWidget級別上顯示的元素數量

看了一下日誌輸出如圖:

有沒有發現一個問題?當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 缺點

經過上面的分析,咱們來看下它的缺點

  • 容易形成沒必要要的刷新
  • 不支持跨頁面(route)的狀態,意思是跨樹,若是不在一個樹中,咱們沒法獲取
  • 數據是不可變的,必須結合StatefulWidget、ChangeNotifier或者Steam使用

InheritedWidget 小結

通過一系列的舉例和驗證,你也基本的掌握了InheritedWidget了吧,這個組件特別適合在同一樹型Widget中,抽象出公有狀態,每個子Widget或者孫Widget均可以獲取該狀態,咱們還能夠經過手段控制rebuild的粒度來優化重繪邏輯,但它更適合從上往下傳遞,若是是從下往上傳遞,咱們如何作到呢?請往下看,立刻給你解答

Notification

它是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",)
        ],
      ),
    );
  }
}
  • 定義TestNotification通知的實現
  • WidgetNotification 負責通知結果,經過RaisedButton的點擊事件,將數據a傳遞出去,經過Notification提供的dispatch方法向上傳遞
  • WidgetListener經過Widget NotificationListener來監聽數據變化,最終經過setState變動數據
  • WidgetNotification 實例化了兩次,一次在NotificationListener的樹內部,一個在NotificationListener的外部,通過測試發現,在外部的WidgetNotification並不能通知到內容變化。

因此說在使用Notification的時候要注意,若是遇到沒法收到通知的狀況,考慮是不是Notification 未在NotificationListener的內部發出通知,這個必定要注意。

一樣的思路,我想看下Notification是如何刷新Ui的
在代碼里加入了跟通知可有可無的WidgetC


這麼看來,你覺得是Notification致使的嗎?我把這個註釋掉,如圖

再運行看下,連續點擊了八次

原來是State的緣由,那麼這種狀況咱們如何優化呢?這就用到了Stream了,請接着往下繼續看哦。

推薦閱讀

flutter-notifications-bubble-up-and-values-go-down

notification

Notification缺點

  • 不支持跨頁面(route)的狀態,準備的說不支持NotificationListener同級或者父級Widget的狀態通知
  • 自己不支持刷新UI,須要結合State使用
  • 若是結合State,會致使整個UI的重繪,效率底下不科學

Notification小結

使用起來很簡單,但在刷新UI方面須要注意,若是頁面複雜度很高,致使可有可無的組件跟着刷新,得不償失,還須要另找蹊徑,躲開這些坑,下面我來介紹如何完美躲閃,重磅來襲Stream。

Stream

它實際上是純Dart的實現,跟Flutter沒什麼關係,扯上關係的就是用StreamBuilder來構建一個Stream通道的Widget,像知名的rxdart、BloC、flutter_redux全都用到了Stream的api。因此學習它纔是咱們掌握狀態管理的一個關鍵

推薦閱讀
我本身寫的StreamBuilder源碼分析
大神寫的Stream全面分析

Stream 缺點

  • api生澀,很差理解
  • 須要定製化,才能知足更復雜的場景

缺點偏偏是它的優勢,保證了足夠靈活,你更可基於它作一個好的設計,知足當下業務的設計。

小結

經過對State、InheritedWidget、Notification、Stream的學習,你是否是以爲,Flutter的狀態管理也就這些了呢?不必定哈,我也在不斷的學習,若是碰到新的技術,繼續分享給大家哦。難道這就完了嗎?沒有,其實咱們只是學了第一步,是什麼,如何用,尚未討論怎麼用好呢?須要什麼標準嗎,固然有,下面經過個人項目實戰經驗來提出一個基本原則,超過這個原則你就是在破壞平衡,請往下看。

狀態管理的使用原則

局部管理優於全局

這個原則來源於,Flutter的性能優化,局部刷新確定比全局刷新要好不少,那麼咱們在管理狀態的同時,也要考慮該狀態究竟是局部仍是全局,從而編寫正確的邏輯。

保持數據安全性

用「_」私有化狀態,由於當開發人員衆多,當別人看到你的變量的時候,第一反應可能不是找你提供的方法,而是直接對變量操做,那就有可能出現想不到的後果,若是他只能調用你提供的方法,那他就要遵循你方法的邏輯,避免數據被處理錯誤。

考慮頁面從新build帶來的影響

不少時候頁面的重建都會調用build函數,也就是說,在一個生命週期內,build函數是屢次被調用的,因此你就要考慮數據的初始化或者刷新怎麼樣才能合理。

使用成熟狀態管理庫弊端

  • 增長代碼複雜性
  • 框架bug修復須要時間等待
  • 不理解框架原理致使使用方式不對,反而帶來更多問題
  • 選型錯誤致使不符合應用要求
  • 與團隊風格衝突不適用

經過了解它們的弊端來規避一些風險,綜合考慮,選框架不易,且行且珍惜。

選型原則

  • 侵入性
  • 擴展性
  • 高性能
  • 安全性
  • 駕馭性
  • 易用性
  • 範圍性

全部的框架都有侵入性,你贊成嗎?不一樣意請左轉,前面有個坑,你能夠跳過去。目前侵入性比較高的表明ScopedModel,爲啥?由於它是用extend實現的,須要繼承實現的基本不是什麼好實現,你贊成嗎?同上。
擴展性就不用說了,若是你選擇的框架只能使用它提供的幾個入口,那麼請你放棄使用它。高性能也是很重要的,這個須要明白它的原理,看它到底如何作的管理。安全性也很重要,看他數據管理通道是否安全穩定。駕馭性,你說你都不理解你就敢用,出了問題找誰?若是駕馭不了也不要用。易用性你們應該都明白,若是用它一個框架須要N多配置,N多實現,放棄吧,不合適。簡單纔是硬道理。

範圍性
這個特色是flutter中比較明顯的,框架選型必定要考慮框架的適用範圍,究竟是適合作局部管理,仍是適合全局管理,要作一個實際的考量。

推薦用法

若是是初期,建議多使用Stream、State、Notification來自行處理,順便學習源碼,多理解,多實踐。有架構能力的就能夠着手封裝了,提供更簡單的使用方式
若是是後期,固然也是在前面的基礎之上,再去考慮使用Provider、redux等複雜的框架,原則上要吃透源碼,不然不建議使用。

注意

你覺得使用框架就能萬事大吉了?性能優化是一個不變的話題,包括Provider在內的,若是你使用不當,照樣出現頁面的性能損耗嚴重,因此你又回到了爲啥會這樣,請你學習上面的底層邏輯,謝謝🙏

總結

經過這期分享,你是否是對Flutter的狀態管理有了一個從新的認識呢?若是對你有幫住,請點一下下面的贊哦。謝謝🙏。

相關文章
相關標籤/搜索