Flutter | 狀態(tài)管理探索篇——Redux(二)

前言

Flutter的很多靈感來自于React,它的設(shè)計(jì)思想是數(shù)據(jù)與視圖分離,由數(shù)據(jù)映射渲染視圖。所以在Flutter中,它的Widget是immutable的,而它的動態(tài)部分全部放到了狀態(tài)(State)中。于是狀態(tài)管理自然便成了我們密切關(guān)注的對象。

在之前我們已經(jīng)討論了關(guān)于在flutter中使用scoped_model進(jìn)行狀態(tài)管理的應(yīng)用。文章發(fā)出后,有許多同學(xué)都在問我,到底redux和scoped到底誰更好。

這個系列將會從這幾個狀態(tài)管理方案進(jìn)行深入研究:

  • Scoped_model
  • redux
  • BLoC
  • 對比總結(jié)篇

所以今天要和大家介紹的是在flutter中使用Redux進(jìn)行狀態(tài)管理。
我希望各位在閱讀這篇文章之前,先仔細(xì)思考以下這幾個問題。

  • 什么是redux
  • redux給我們了什么好處,我們?yōu)槭裁匆褂盟?/li>
  • 它的基本思想是什么
  • redux是否真的適合我們

ok,我們開始正式的介紹redux。

Redux

為什么需要狀態(tài)管理

在我們一開始構(gòu)建應(yīng)用的時候,也許很簡單。我們有一些狀態(tài),直接把他們映射成視圖就可以了。這種簡單應(yīng)用可能并不需要狀態(tài)管理。

image

但是隨著功能的增加,你的應(yīng)用程序?qū)袔资畟€甚至上百個狀態(tài)。這個時候你的應(yīng)用應(yīng)該會是這樣。

image

Wow,這是什么鬼。我們很難再清楚的測試維護(hù)我們的狀態(tài),因?yàn)樗瓷先?shí)在是太復(fù)雜了!而且還會有多個頁面共享同一個狀態(tài),例如當(dāng)你進(jìn)入一個文章點(diǎn)贊,退出到外部縮略展示的時候,外部也需要顯示點(diǎn)贊數(shù),這時候就需要同步這兩個狀態(tài)。

這時候,我們便迫切的需要一個架構(gòu)來幫助我們理清這些關(guān)系,狀態(tài)管理框架應(yīng)運(yùn)而生。

redux是什么

Redux是一種單向數(shù)據(jù)流架構(gòu),可以輕松開發(fā),維護(hù)和測試應(yīng)用程序。

image
  • 我們在Redux中,所有的狀態(tài)都儲存在Store里。這個Store會放在App頂層。
  • View拿到Store儲存的狀態(tài)(State)并把它映射成視圖。View還會與用戶進(jìn)行交互,用戶點(diǎn)擊按鈕滑動屏幕等等,這時會因?yàn)榻换バ枰獢?shù)據(jù)發(fā)生改變。
  • Redux讓我們不能讓View直接操作數(shù)據(jù),而是通過發(fā)起一個action來告訴Reducer,狀態(tài)得改變啦。
  • 這時候Reducer接收到了這個action,他就回去遍歷action表,然后找到那個匹配的action,根據(jù)action生成新的狀態(tài)并把新的狀態(tài)放到Store中。
  • Store丟棄了老的狀態(tài)對象,儲存了新的狀態(tài)對象后,就通知所有使用到了這個狀態(tài)的View更新(類似setState)。這樣我們就能夠同步不同view中的狀態(tài)了。

Lets do it!

這里我們以一個最簡單的CountApp舉例。簡單介紹flutter_redux/redux的用法。該項(xiàng)目完整代碼已上傳Github。

這是一個在不同頁面使用Redux共享狀態(tài)信息的app。這兩個頁面都依賴于一個數(shù)字,這個數(shù)字會隨著我們按下按鈕的次數(shù)而增加。

image

第一步:添加依賴

image

我們這里使用了redux/flutter_redux庫,它們都是由Brian Egan大神編寫的。其中flutter_redux是用來簡化redux的使用的。

第二步:創(chuàng)建State

我們剛才介紹了Redux的流程,狀態(tài)是由reducer生成并儲存在Store里面的。Store更新狀態(tài)的時候,并不是更改原來的狀態(tài)對象,而是直接將reducer生成的新的狀態(tài)對象替換掉老的狀態(tài)對象。所以,我們的狀態(tài)應(yīng)該是immutable的。

import 'package:meta/meta.dart';
/**
 * State中所有屬性都應(yīng)該是只讀的
 */
@immutable
class CountState{
  int _count;
  get count => _count;

  CountState(this._count);
}

第三步:創(chuàng)建action

可能各位最開始接觸的時候?qū)ction還會摸不著頭腦。action到底是什么?View如何發(fā)出action。其實(shí),action只是我們對狀態(tài)進(jìn)行操作方法的一個代號而已。在我們這個應(yīng)用中,唯一的一個功能就是讓count的值+1,所以我們這里只有一個action。

/**
 * 定義操作該State的全部Action
 * 這里只有增加count一個動作
 */
enum Action{
  increment
}

第四步:創(chuàng)建reducer

reducer是我們的狀態(tài)生成器,它接收一個我們原來的狀態(tài),然后接收一個action,再匹配這個action生成一個新的狀態(tài)。

/**
 * reducer會根據(jù)傳進(jìn)來的action生成新的CountState
 */
CountState reducer(CountState state,action){
  //匹配Action
    if(action == Action.increment){
      return CountState(state.count+1);
    }
    return state;
}

第五步:創(chuàng)建store

Store接收一個reducer,以及初始化State,我們想用Redux管理全局的狀態(tài)的話,需要將store儲存在應(yīng)用的入口才行。而在應(yīng)用打開時要先初始化一次應(yīng)用的狀態(tài)。所以在State中添加一個初始化的函數(shù)。

//這段代碼寫在State中
CountState.initState(){ _count = 0;}
//應(yīng)用頂層
void main() {
  final store =
      Store<CountState>(reducer, initialState: CountState.initState());
  runApp(new MyApp(store));
}

第六步:將Store放入頂層

flutter_redux提供了一個很棒的widget叫做StoreProvider,它的用法也很簡單,接收一個store,和child Widget。

class MyApp extends StatelessWidget {
  final Store<CountState> store;

  MyApp(this.store);

  @override
  Widget build(BuildContext context) {
    return StoreProvider<CountState>(
      store: store,
      child: new MaterialApp(
        title: 'Flutter Demo',
        theme: new ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: TopScreen(),
      ),
    );
  }
}

第六步:在子頁面中獲取Store中的state

這里建議大家把實(shí)際代碼對照下面的解釋一起看。

StoreConnector<CountState,int>(
              converter: (store) => store.state.count,
              builder: (context, count) {
                return Text(
                  count.toString(),
                  style: Theme.of(context).textTheme.display1,
                );
              },
            ),

要想獲取store我們需要使用StoreConnector<S,ViewModel>。StoreConnector能夠通過StoreProvider找到頂層的store。而且能夠在state發(fā)生變化時rebuilt Widget。


image
  • 首先這里需要強(qiáng)制聲明類型,S代表我們需要從store中獲取什么類型的state,ViewModel指的是我們使用這個State時的實(shí)際類型。
  • 然后我們需要聲明一個converter<S,ViewModel>,它的作用是將Store轉(zhuǎn)化成實(shí)際ViewModel將要使用的信息,比如我們這里實(shí)際上要使用的是count,所以這里將count提取出來。
  • builder是我們實(shí)際根據(jù)state創(chuàng)建Widget的地方,它接收一個上下文context,以及剛才我們轉(zhuǎn)化出來的ViewModel,所以我們就只需要把拿到的count放進(jìn)Text Widget中進(jìn)行渲染就好了。

第七步:發(fā)出action

我們這個應(yīng)用在第二個頁面中,通過點(diǎn)擊floatingActionButton發(fā)出了action,并通知reducer生成了新的狀態(tài)。

floatingActionButton: StoreConnector<CountState,VoidCallback>(
        converter: (store) {
          return () => store.dispatch(Action.increment);
        },
        builder: (context, callback) {
          return FloatingActionButton(
            onPressed: callback,
            child: Icon(Icons.add),
          );
        },
      ),
  • 同樣,我們還是使用StoreConnector<S,ViewModel>。這里由于是發(fā)出了一個動作,所以是VoidCallback。
  • store.dispatch發(fā)起一個action,任何中間件都會攔截該操作,在運(yùn)行中間件后,操作將被發(fā)送到給定的reducer生成新的狀態(tài),并更新狀態(tài)樹。

以上便是在flutter中使用redux共享狀態(tài)信息的全部內(nèi)容。

Q&A

ViewModel性能優(yōu)化

我們的StoreConnector能夠?qū)tore提取出信息并轉(zhuǎn)化成ViewModel,這里其實(shí)是有一個性能優(yōu)化的點(diǎn)的。我們這里的例子非常簡單,它的ViewModel就只是一個int的值,當(dāng)我們ViewModel很復(fù)雜的時候,我們可以使用StoreConnector的distinct屬性進(jìn)行性能優(yōu)化。使用方法很簡單:需要我們在ViewModel中重寫[==] and [hashCode] 方法,然后把distinct屬性設(shè)為true。

如何處理異步數(shù)據(jù)

Redux提供了一種簡單的方法來更新應(yīng)用程序的狀態(tài)以響應(yīng)同步操作。但是,它缺少處理異步代碼的工具。我們?nèi)绾螒?yīng)對異步相應(yīng)呢。

這里就需要一個interrupt來處理異步請求,然后再發(fā)出新的action通知reducer生成新的State了。
這里有brianegan大神寫的另外一個幫助在flutter中使用redux處理異步請求的庫redux_thunk。我會在之后的文章中詳細(xì)介紹如何在redux中處理異步操作。

你認(rèn)為redux真的適合flutter嗎

我們發(fā)現(xiàn),redux的確能夠在flutter中很好的工作。在react中數(shù)據(jù)是沒有上行能力的,所以通過數(shù)據(jù)單向流動形成一個環(huán)來進(jìn)行狀態(tài)管理??瓷先ニ坪醪]有把flutter中的優(yōu)勢完全發(fā)揮出來。在這個簡單的例子中我們也可以看出,使用redux還是稍微有些麻煩的,用的不好,可能會陷入redux地獄。學(xué)習(xí)成本偏高也是它的一大痛點(diǎn)。

當(dāng)然,redux這套狀態(tài)管理架構(gòu)已經(jīng)比較成熟,假如您已經(jīng)習(xí)慣redux,也能夠快速通過flutter_redux輕松構(gòu)建屬于您的狀態(tài)管理應(yīng)用。

那么你現(xiàn)在如何看待redux呢?

寫在最后

本次所用到的代碼已經(jīng)上傳Github:
https://github.com/Vadaski/Flutter-Notebook/tree/master/mecury_project/example/redux_demo

這篇文章參考了以下資料

了解更多

你能在這些地方了解更多關(guān)于flutter-redux

如果您對flutter_redux還有任何看法或者文章的建議或者文章中有任何不對之處,歡迎在下方評論區(qū)以及我的郵箱1652219550a@gmail.com留言,我會在24小時內(nèi)與您聯(lián)系!

按理說下一章我們將探索BLoC在Flutter中的實(shí)踐,而BLoC非常Reactive Programming,所以我決定先讓大家了解一些dart:Stream的知識再介紹它,所以下一篇文章我們會介紹Stream以及流式編程,敬請關(guān)注。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容