• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • Flutter 狀態管理之Provider

    標簽: flutter

    flutter中狀態管理是重中之重,每當談這個話題,總有說不完的話。

    在正式介紹 Provider 為什么我們需要狀態管理。如果你已經對此十分清楚,那么建議直接跳過這一節。
    如果我們的應用足夠簡單,Flutter 作為一個聲明式框架,你或許只需要將 數據 映射成 視圖 就可以了。你可能并不需要狀態管理,就像下面這樣。

    但是隨著功能的增加,你的應用程序將會有幾十個甚至上百個狀態。這個時候你的應用應該會是這樣。

    這又是什么鬼。我們很難再清楚的測試維護我們的狀態,因為它看上去實在是太復雜了!而且還會有多個頁面共享同一個狀態,例如當你進入一個文章點贊,退出到外部縮略展示的時候,外部也需要顯示點贊數,這時候就需要同步這兩個狀態。
    Flutter 實際上在一開始就為我們提供了一種狀態管理方式,那就是 StatefulWidget。但是我們很快發現,它正是造成上述原因的罪魁禍首。
    State 屬于某一個特定的 Widget,在多個 Widget 之間進行交流的時候,雖然你可以使用 callback 解決,但是當嵌套足夠深的話,我們增加非常多可怕的垃圾代碼。
    這時候,我們便迫切的需要一個架構來幫助我們理清這些關系,狀態管理框架應運而生。

    Provider 是什么

    通過使用Provider而不用手動編寫InhertedWidget,您將獲取自動分配、延遲加載、大大減少每次創建新類的代碼。

    首先在yaml中添加,具體版本號參考:官方Provider pub,當前版本號是4.1.3.

      Provider: ^4.1.3
    

    然后運行

    flutter pub get
    

    獲取到最新的包到本地,在需要的文件夾內導入

    import 'package:provider/provider.dart';
    

    簡單例子

    我們還用點擊按鈕新增數字的例子

    首先創建存儲數據的Model

    class ProviderModel extends ChangeNotifier {
    
    	int _count=0;
    	ProviderModel();
       void plus() {
       /// 在數據變動的時候通知監聽者刷新UI
        _count = _count + 1;
        notifyListeners();
      }
    }
    

    構造view

    /// 使用Consumer來監聽全局刷新UI
    Consumer<ProviderModel>(
            builder:
                (BuildContext context, ProviderModel value, Widget child) {
              print('Consumer 0 刷新');
              _string += 'c0 ';
              return _Row(
                value: value._count.toString(),
                callback: () {
                  context.read<ProviderModel>().plus();
                },
              );
            },
            child: _Row(
              value: '0',
              callback: () {
                context.read<ProviderModel>().plus();
              },
            ),
          )
    

    測試下看下效果:

    單個Model多個小部件分別刷新(局部刷新)

    單個model實現單個頁面多個小部件分別刷新,是使用Selector<Model,int>來實現,首先看下構造函數:

    class Selector<A, S> extends Selector0<S> {
      /// {@macro provider.selector}
      Selector({
        Key key,
        @required ValueWidgetBuilder<S> builder,
        @required S Function(BuildContext, A) selector,
        ShouldRebuild<S> shouldRebuild,
        Widget child,
      })  : assert(selector != null),
            super(
              key: key,
              shouldRebuild: shouldRebuild,
              builder: builder,
              selector: (context) => selector(context, Provider.of(context)),
              child: child,
            );
    }
    

    可以看到Selector繼承了Selector0,再看Selector關鍵build代碼:

    class _Selector0State<T> extends SingleChildState<Selector0<T>> {
      T value;
      Widget cache;
      Widget oldWidget;
    
      @override
      Widget buildWithChild(BuildContext context, Widget child) {
        final selected = widget.selector(context);
    
        var shouldInvalidateCache = oldWidget != widget ||
            (widget._shouldRebuild != null &&
                widget._shouldRebuild.call(value, selected)) ||
            (widget._shouldRebuild == null &&
                !const DeepCollectionEquality().equals(value, selected));
        if (shouldInvalidateCache) {
          value = selected;
          oldWidget = widget;
          cache = widget.builder(
            context,
            selected,
            child,
          );
        }
        return cache;
      }
    }
    

    根據我們傳入的_shouldRebuild來判斷是否需要更新,如果需要更新則執行widget.build(context,selected,child),否則返回已經緩存的cache.當沒有_shouldRebuild參數時則根據widget.selector(ctx)的返回值判斷是否和舊值相等,不等則更新UI

    所以我們不寫shouldRebuild也是可以的。

    局部刷新用法

      Widget build(BuildContext context) {
        print('page 1');
        _string += 'page ';
        return Scaffold(
          appBar: AppBar(
            title: Text('Provider 全局與局部刷新'),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Text('全局刷新<Consumer>'),
                Consumer<ProviderModel>(
                  builder:
                      (BuildContext context, ProviderModel value, Widget child) {
                    print('Consumer 0 刷新');
                    _string += 'c0 ';
                    return _Row(
                      value: value._count.toString(),
                      callback: () {
                        context.read<ProviderModel>().plus();
                      },
                    );
                  },
                  child: _Row(
                    value: '0',
                    callback: () {
                      context.read<ProviderModel>().plus();
                    },
                  ),
                ),
                SizedBox(
                  height: 40,
                ),
                Text('局部刷新<Selector>'),
                Selector<ProviderModel, int>(
                  builder: (ctx, value, child) {
                    print('Selector 1 刷新');
                    _string += 's1 ';
                    return Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Text('Selector<Model,int>次數:' + value.toString()),
                        OutlineButton(
                          onPressed: () {
                            context.read<ProviderModel>().plus2();
                          },
                          child: Icon(Icons.add),
                        )
                      ],
                    );
                  },
                  selector: (ctx, model) => model._count2,
                  shouldRebuild: (m1, m2) {
                    print('s1:$m1 $m2 ${m1 != m2 ? '不相等,本次刷新' : '數據相等,本次不刷新'}');
                    return m1 != m2;
                  },
                ),
                SizedBox(
                  height: 40,
                ),
                Text('局部刷新<Selector>'),
                Selector<ProviderModel, int>(
                  selector: (context, model) => model._count3,
                  shouldRebuild: (m1, m2) {
                    print('s2:$m1 $m2 ${m1 != m2 ? '不相等,本次刷新' : '數據相等,本次不刷新'}');
                    return m1 != m2;
                  },
                  builder: (ctx, value, child) {
                    print('selector 2 刷新');
                    _string += 's2 ';
                    return Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Text('Selector<Model,int>次數:' + value.toString()),
                        OutlineButton(
                          onPressed: () {
                            ctx.read<ProviderModel>().plus3();
                          },
                          child: Icon(Icons.add),
                        )
                      ],
                    );
                  },
                ),
                SizedBox(
                  height: 40,
                ),
                Text('刷新次數和順序:↓'),
                Text(_string),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    OutlineButton(
                      child: Icon(Icons.refresh),
                      onPressed: () {
                        setState(() {
                          _string += '\n';
                        });
                      },
                    ),
                    OutlineButton(
                      child: Icon(Icons.close),
                      onPressed: () {
                        setState(() {
                          _string = '';
                        });
                      },
                    )
                  ],
                )
              ],
            ),
          ),
        );
      }
    
    

    效果:

    當我們點擊局部刷新s1,執行s1builds1不相等,s2相等不刷新。輸出:

    flutter: s2:5 5 數據相等,本次不刷新
    flutter: s1:6 7 不相等,本次刷新
    flutter: Selector 1 刷新
    flutter: Consumer 0 刷新
    

    當點擊s2,s2的值不相等刷新UI,s1數據相等,不刷新UI.

    flutter: s2:2 3 不相等,本次刷新
    flutter: selector 2 刷新
    flutter: s1:0 0 數據相等,本次不刷新
    flutter: Consumer 0 刷新
    

    可以看到上邊2次Consumer每次都刷新了,我們探究下原因。

    Consumer 全局刷新

    Consumer繼承了SingleCHildStatelessWidget,當我們在ViewModel中調用notification則當前widget被標記為dirty,然后在build中執行傳入的builder函數,在下幀則會刷新UI

    Selector<T,S>則被標記dirty時執行_Selector0State中的buildWithChild(ctx,child)函數時,根據selected_shouldRebuild來判斷是否需要執行widget.builder(ctx,selected,child)(刷新UI).

    其他用法

    多model寫法

    只需要在所有需要model的上級包裹即可,當我們一個page需要2model的時候,我么通常這樣子寫:

    class BaseProviderRoute extends StatelessWidget {
      BaseProviderRoute({Key key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MultiProvider(
          providers: [
            ChangeNotifierProvider<ProviderModel>(
              create: (_) => ProviderModel(),
            ),
            ChangeNotifierProvider<ProviderModel2>(create: (_) => ProviderModel2()),
          ],
          child: BaseProvider(),
        );
      }
    }
    

    當然是用的時候和單一model一致的。

      Selector<ProviderModel2, int>(
        selector: (context, model) => model.value,
        builder: (ctx, value, child) {
          print('model2 s1 刷新');
          _string += 'm2s1 ';
          return Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Selector<Model2,int>次數:' + value.toString()),
              OutlineButton(
                onPressed: () {
                  ctx.read<ProviderModel2>().add(2);
                },
                child: Icon(Icons.add),
              )
            ],
          );
        },
      ),
    
    

    watch && read

    watch源碼是Provider.of<T>(this),默認Provider.of<T>(this)listen=true.

    static T of<T>(BuildContext context, {bool listen = true}){
    final inheritedElement = _inheritedElementOf<T>(context);
    
        if (listen) {
          context.dependOnInheritedElement(inheritedElement);
        }
    
        return inheritedElement.value;
    }
    

    read源碼是Provider.of<T>(this, listen: false),watch/read只是寫法簡單一點,并無高深結構。

    當我們想要監聽值的變化則是用watch,當想調用model的函數時則使用read

    參考

    版權聲明:本文為iOS_And_Swift原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/iOS_And_Swift/article/details/108073659

    智能推薦

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    統計學習方法 - 樸素貝葉斯

    引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...

    styled-components —— React 中的 CSS 最佳實踐

    https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...

    猜你喜歡

    基于TCP/IP的網絡聊天室用Java來實現

    基于TCP/IP的網絡聊天室實現 開發工具:eclipse 開發環境:jdk1.8 發送端 接收端 工具類 運行截圖...

    19.vue中封裝echarts組件

    19.vue中封裝echarts組件 1.效果圖 2.echarts組件 3.使用組件 按照組件格式整理好數據格式 傳入組件 home.vue 4.接口返回數據格式...

    劍指Offer39-調整數組順序使奇數位于偶數前面

    一開始想著用冒泡排序的方法來做,但是bug還是很多,后來看了評論區答案,發現直接空間換時間是最簡單的,而且和快排的寫法是類似的。...

    【一只蒟蒻的刷題歷程】【藍橋杯】歷屆試題 九宮重排 (八數碼問題:BFS+集合set)

    資源限制 時間限制:1.0s 內存限制:256.0MB 問題描述 如下面第一個圖的九宮格中,放著 1~8 的數字卡片,還有一個格子空著。與空格子相鄰的格子中的卡片可以移動到空格中。經過若干次移動,可以形成第二個圖所示的局面。 我們把第一個圖的局面記為:12345678. 把第二個圖的局面記為:123.46758 顯然是按從上到下,從左到右的順序記錄數字,空格記為句點。 本題目的任務是已知九宮的初態...

    dataV組件容器寬高發生變化后,組件不會自適應解決方法

    項目中需要大屏幕數據展示,于是使用了dataV組件,但是使用是發現拖動瀏覽器邊框,dataV組件顯示異常,如圖: 于是查了官網,官網的解釋如下:   于是按照官網的意思編寫代碼: 于是可以自適應了...

    精品国产乱码久久久久久蜜桃不卡