Flutter 狀態管理一鍋端:第一章 Provider


序言

任何人事物都有狀態,水結冰、天起大霧、人生氣這些未嘗不是狀態呢?那麼對於程序而言,對於語言來講,他們的狀態又是什麼呢?咱們知道前端有那麼幾個大的框架Vue.js,React.js,包括但不限於小程序語法、移動端,他們都會有本身活躍的生態環境,同時也會衍生出本身的狀態管理工具,像Vuex,像Vue的男友同樣,如影隨行,像Mobx Redux,好像他們無處不在,爲何說狀態管理是那麼的重要呢,有時候不用狀態管理,簡簡單單、單單純純不就挺好的。雖然話是這麼說,但是真正在企業項目的時候,你們又都說組件化開發,組件化開發,那就形成了一個問題,這個數據,這個值怎麼辦,傳來傳去不怕傳丟了,那這段路讓咱們一塊兒來聊聊狀態管理在Flutter中又是什麼的一個角色,誰和誰又是「情敵」呢 寫分享第一點要考慮的就是起名字,就是一段視頻 ,封面到底怎麼樣纔可以吸引人,文章也不例外,一個你們 都樂於去閱讀的可能是那些名字聽起來就炸天的,好比狀態管理看這篇就夠了Flutter狀態管理終極祕籍等等,固然我們也不肯去作震驚派,因此暫且稱爲Flutter 狀態管理一鍋端 靈感是來自以前看過的一篇文章,畢竟人家寫的也是真的很好,但願能趁上這個名字。閒扯了那麼多,仍是沒有一點技術性在,那開始吧……前端

  • 文章字數 3143
  • 閱讀建議 能夠跟着敲一敲效果更佳

第一章 走進官方推薦的解決方案Provider

在開始寫以前,我決定從官方文檔找切入,雖然一些中文的文檔寫的也很好,我們從flutter.dev/看會不會發現什麼蛛絲馬跡 git

120601.png
我麼發現官網還真的有關於狀態的描述

120602.png

映入眼簾的即是這個小動畫,大體意思講的是添加購物車的demo,也就是說探索Flutter時,有時須要跨應用程序在屏幕之間共享應用程序狀態。固然咱們也必須考慮不少問題 github

120603.gif
那對於從事Ios 或者安卓的開發者來講,真的須要重新的角度去看問題嘛,對於前端開發者來講早已司空見慣。 能夠從頭開始重建UI的某些部分,而不用對其進行修改。Flutter的速度足夠快,即便須要時也能夠在每一幀上執行。還有說道 UI = F(state)

1.1 瞭解狀態

短時 (ephemeral) 狀態

不須要使用狀態管理架構(例如 ScopedModel, Redux)去管理這種狀態。你須要用的只是一個 StatefulWidget。 在下方你能夠看到一個底部導航欄中當前被選中的項目是如何被被保存在 _MyHomepageState 類的 _index 變量中。小程序

class MyHomepage extends StatefulWidget {
  @override
  _MyHomepageState createState() => _MyHomepageState();
}

class _MyHomepageState extends State<MyHomepage> {
  int _index = 0; // 這個index即是短時的,別的地方也不須要去訪問它不是嗎

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      currentIndex: _index,
      onTap: (newIndex) {
        setState(() {
          _index = newIndex;
        });
      },
      // ... items ...
    );
  }
}
複製代碼

應用狀態

若是你想在你的應用中的多個部分之間共享一個非短時的狀態,而且在用戶會話期間保留這個狀態,咱們稱之爲應用狀態(有時也稱共享狀態)。什麼意思呢,就是說一個狀態在A頁面也要用在B頁面也要使用,就像一個渣男同樣,和誰都有染 api

03.png

也就是說哪位女士都有點狀態上的聯繫 在這裏緩存

Use React for ephemeral state that doesn't matter to the app globally and doesn't mutate in complex ways. For example, a toggle in some UI element, a form input state. Use Redux for state that matters globally or is mutated in complex ways. For example, cached users, or a post draft.markdown

Sometimes you'll want to move from Redux state to React state (when storing something in Redux gets awkward) or the other way around (when more components need to have access to some state that used to be local).架構

The rule of thumb is: do whatever is less awkward.app

翻譯過來就是框架

將React用於短暫狀態,該狀態對應用程序全局可有可無,而且不會以複雜的方式進行更改。 例如,在某些UI元素中切換,即表單輸入狀態。 將Redux用於全局重要的狀態或以複雜方式突變的狀態。 例如,緩存的用戶或後期草稿。

有時,您可能但願從Redux狀態轉換爲React狀態(當在Redux中存儲某些內容變得笨拙時),或者反過來(當更多組件須要訪問某些曾經是本地的狀態時)。

經驗法則是:作些不太尷尬的事情。

大體就是這些意思

2 開始實踐

從官方看到,這也印證了一點,它是官方建議使用的狀態管理工具 首先我們先創建幾個互不相干的文件來一下

  • MyPageA
import 'package:flutter/material.dart';

class MyPageA extends StatefulWidget {
  MyPageA({Key key}) : super(key: key);

  @override
  _MyPageAState createState() => _MyPageAState();
}

class _MyPageAState extends State<MyPageA> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Center(
          child: Text('我是A頁面'),
        ),
      ),
    );
  }
}

複製代碼
  • MyPageB
  • MyPageC 後兩個一樣的道理。那麼在首頁呢我們寫三個按鈕,分別能夠跳向不一樣的按鈕,在這裏爲了演示案例,我們就不作代碼的封裝等問題。
child: Center(
          child: Column(
            children: <Widget>[
              RaisedButton(
                onPressed: () {},
                child: Text('我是跳轉到A頁面的按鈕'),
              ),
              RaisedButton(
                onPressed: () {},
                child: Text('我是跳轉到B頁面的按鈕'),
              ),
              RaisedButton(
                onPressed: () {},
                child: Text('我是跳轉到C頁面的按鈕'),
              )
            ],
          ),
        ),
複製代碼

如今指望是這樣子的咱們在頁面A作測試

child: Column(
                children: <Widget>[
                  Container(
                    child: RaisedButton(
                      onPressed: () {},
                      child: Text('+'),
                    ),
                  ),
                  Container(
                    child: Text('如今的值是'),
                  ),
                ],
              )),
複製代碼

想必已經知道要作個什麼效果了就是沒點擊一次的時候,默認值就加一

04.png

Container(
                    child: RaisedButton(
                      onPressed: () {
                        setState(() {
                          count = count + 1;
                        });
                      },
                      child: Text('+'),
                    ),
                  ),
                  Container(
                    child: Text('如今的值是$count'),
                  ),
複製代碼

如今已經能夠實現了,那麼咱們要在首頁也顯示這個值怎麼辦呢,這至關因而他的父親也想看的值,就像是,兒子過年的時候收了一大筆壓歲錢,此時長輩指望知道咱收了多少壓歲錢而後,沒收好伐 這樣子吧,父親給個方法過來,也就是給個袋子過來,我把壓歲錢裝進去

void callback() {
    print('我是首頁的方法');
  }
複製代碼

在跳轉的時候,把方法傳過去,Flutter 中能夠任意的傳遞他們

Navigator.push(
                    context,
                    new MaterialPageRoute(
                        builder: (context) => MyPageA(myCallBack)),
                  );
複製代碼

但是會有一個問題,可是對於全局應用狀態來講你須要在不一樣的地方進行修改,可能須要大量傳遞迴調函數 寫到這兒,突然有個頗有意思的想法,那就是我們腦海中有個這個想法,

05.png

  • 父親:父組件便是main.dart 文件
  • 大兒子:子組件一也就是MyPageA 文件
  • 二女兒:子組件二MyPageB文件
  • 老少:子組件三MyPageC文件 目前是這樣的一個狀況
void myCallBack(val) {
    print('我是父親,這是個人收錢袋子,從孩子哪裏沒收的壓歲錢是$val');
  }
複製代碼
onPressed: () {
                        setState(() {
                          count = count + 1;
                        });
                        // 調用回調
                        widget.callback(count);
                      },
複製代碼

那這樣就會有一個問題,在開發的過程當中,就會出現大量的回調函數,這裏就引用官方推薦的一種方式,provider package 須要理解3個概念

  • ChangeNotifier 這個是Flutter SDK 裏用來向你們發送通知的一個類,相似於村裏的喇叭 用來通知村民一些事情的變化等 那麼就像如上提到的壓碎錢的案例,怎們才能把壓碎錢在父親和幾個孩子之間進行來回的傳遞呢,接下來我打算用一下這個相似於廣播的東西。 在lib下新建一個文件夾命名provider lib/provider/money_provider.dart
import 'package:flutter/material.dart';

// MoneyProvider 這個類繼承自發布者
class MoneyProvider extends ChangeNotifier {
  /// 這裏就不說是數據了我們暫且稱爲私有數據,並_開頭命名
  num _money = 0;
  // 把數據get 一下,相似於暴露
  num get money => _money;
  // 定義一個沒有返回值的方法,主要是用來增長本身的壓歲錢並展現給其餘家裏人
  void addMoneyAndShowOthers() {
    _money = _money + 1;
    // 該調用告訴正在偵聽此模型的小部件進行重建。
    notifyListeners();
  }
}

複製代碼
  • ChangeNotifierProvider ChangeNotifierProvider widget 能夠向其子孫節點暴露一個 ChangeNotifier 實例。它屬於 provider package。 那咱們喇叭已經搞好了,如今就是要高高掛起。放在全部村民都可以聽的地方 這也就是說hangeNotifierProvider 要放在使用它的部件之上,可是又不能夠放的太上,在這個案例中,咱們暫且放在 Remove Link
    121401.png
    這裏須要注意的是provider 3.2.0
  • 提供者不同意使用的「構建器」,而同意「建立」
  • 不同意使用代理提供程序的「 builder」 /「 initialBuilder」,而分別建議使用「 create」和「 update」 也就是說builder已是不建議使用了,那麼這時候就須要使用新的api
void main() => runApp(
      ChangeNotifierProvider(
        // builder: (context) => MoneyProvider(),
        create: (context) => MoneyProvider(),
        child: MyApp(),
      ),
    );

複製代碼

固然了,當咱們嘗試須要多個共享的狀態的時候呢,好比壓歲錢狀態、孩子有沒有男女友狀態等 就可使用這種方式

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider( create: (context) => MoneyProvider()),
        Provider( create: (context) => MoneyProvider(),),
      ],
      child: MyApp(),
    ),
  );
}
複製代碼
  • Consumer 如上村長大喇叭都已經就緒了,接下來就是村民應該作的事兒了,就是一條通知發出,廣大的村民該怎麼接收呢,這裏就須要一個部件Consumer咱們能夠把它想象成一個Container是同樣的道理,只不過它不僅僅的提供佈局功能
return Consumer<MoneyProvider>(
  builder: (context, state, child) {
    return Text("${state}是什麼呢?");
  },
);
複製代碼

那這個時候,我們就去二女兒頁面去監聽一下試試,那這個時候可能須要我們把有狀態的部件更改成無狀態的部件即StatelessWidget

return Container(
      child: Consumer<MoneyProvider>(
        builder: (context, state, child) {
          return Text("");
        },
      ),
    );
複製代碼

咱們簡單的來看一下這個代碼段

  • MoneyProvider 咱們必須指定要訪問的模型類型(是哪一個provider類)。在這個示例中,咱們要訪問 MoneyProvider 那麼就寫上 Consumer
  • builder 當 ChangeNotifier 發生變化的時候會調用 builder 這個函數
  • context 在每一個 build 方法中都能找到這個參數。
  • state 也就是第2個參數 ChangeNotifier 的實例
  • child 用於優化目的
    121402.png

顯然咱們是成功了的,目前已經能夠顯示狀態數據0

  • Provider.of

有時候就像這樣,咱們多是單獨的想讀出來狀態的數據,還不想讓壓歲錢加1呢,也就是說有的時候你不須要模型中的 數據 來改變 UI,可是你可能仍是須要訪問該數據。 這個時候

Provider.of<MoneyProvider>(context, listen: false)
複製代碼

就像這個樣子

Container(
            child: Column(
          children: <Widget>[
            Text('$moneyFromState'),
            Consumer<MoneyProvider>(
              builder: (context, state, child) {
                return Column(
                  children: <Widget>[
                    RaisedButton(
                      onPressed: () {
                        state.addMoneyAndShowOthers();
                      },
                      child: Text('我是一個按鈕'),
                    ),
                    Text('${state.money}')
                  ],
                );
              },
            ),
          ],
        ))
複製代碼

最後看一下完整的效果吧

121402.png

3 本章總結

這篇provider相關的分享實際上是在1206號左右就開始寫了,因爲種種緣由到今天才寫了差很少,在工做中也有用到狀態管理,方案也是用的provider,整體使用下來尤爲是和後臺通訊結合起來我的並沒以爲很方便,反而是麻煩了點,多是項目比較小的緣由,因此說仍是那句話不近視的話呢,先能夠不要戴眼鏡。因爲平時開發任務也比較重,加上本身也在維護一個全棧的項目。因此這個系列

仍是會不斷的更新,目前github也有幾顆星了,下一篇章打算寫一下Model 類的轉換,但願看到這兒的你可以多多給個鼓勵,但願你也有點收穫,盼望每一個人都能過上兒時夢想搬的生活 這篇分享的相關代碼會同步到githubFlutter 項目開發中不用它就用它的狀態管理一鍋端


-- End but thank you --

相關文章
相關標籤/搜索