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

Vadaski發表於2018-09-23

前言

Flutter的很多靈感來自於React,它的設計思想是資料與檢視分離,由資料對映渲染檢視。所以在Flutter中,它的Widget是immutable的,而它的動態部分全部放到了狀態(State)中。於是狀態管理自然便成了我們密切關注的物件。

在之前我們已經討論了關於在flutter中使用scoped_model進行狀態管理的應用。文章發出後,有許多同學都在問我,到底redux和scoped到底誰更好。

這個系列將會從這幾個狀態管理方案進行深入研究:

  • Scoped_model
  • redux
  • BLoC
  • 對比總結篇

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

  • 什麼是redux
  • redux給我們了什麼好處,我們為什麼要使用它
  • 它的基本思想是什麼
  • redux是否真的適合我們

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

Redux

為什麼需要狀態管理

在我們一開始構建應用的時候,也許很簡單。我們有一些狀態,直接把他們對映成檢視就可以了。這種簡單應用可能並不需要狀態管理。

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

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

Flutter | 狀態管理探索篇——Redux(二)
Wow,這是什麼鬼。我們很難再清楚的測試維護我們的狀態,因為它看上去實在是太複雜了!而且還會有多個頁面共享同一個狀態,例如當你進入一個文章點贊,退出到外部縮略展示的時候,外部也需要顯示點贊數,這時候就需要同步這兩個狀態。

這時候,我們便迫切的需要一個架構來幫助我們理清這些關係,狀態管理框架應運而生。

redux是什麼

Redux是一種單向資料流架構,可以輕鬆開發,維護和測試應用程式。

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

  • 我們在Redux中,所有的狀態都儲存在Store裡。這個Store會放在App頂層。
  • View拿到Store儲存的狀態(State)並把它對映成檢視。View還會與使用者進行互動,使用者點選按鈕滑動螢幕等等,這時會因為互動需要資料發生改變。
  • Redux讓我們不能讓View直接運算元據,而是通過發起一個action來告訴Reducer,狀態得改變啦。
  • 這時候Reducer接收到了這個action,他就回去遍歷action表,然後找到那個匹配的action,根據action生成新的狀態並把新的狀態放到Store中。
  • Store丟棄了老的狀態物件,儲存了新的狀態物件後,就通知所有使用到了這個狀態的View更新(類似setState)。這樣我們就能夠同步不同view中的狀態了。

Lets do it!

這裡我們以一個最簡單的CountApp舉例。簡單介紹flutter_redux/redux的用法。該專案完整程式碼已上傳Github

這是一個在不同頁面使用Redux共享狀態資訊的app。這兩個頁面都依賴於一個數字,這個數字會隨著我們按下按鈕的次數而增加。

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

第一步:新增依賴

Flutter | 狀態管理探索篇——Redux(二)
我們這裡使用了redux/flutter_redux庫,它們都是由Brian Egan大神編寫的。其中flutter_redux是用來簡化redux的使用的。

第二步:建立State

我們剛才介紹了Redux的流程,狀態是由reducer生成並儲存在Store裡面的。Store更新狀態的時候,並不是更改原來的狀態物件,而是直接將reducer生成的新的狀態物件替換掉老的狀態物件。所以,我們的狀態應該是immutable的。

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

  CountState(this._count);
}
複製程式碼

第三步:建立action

可能各位最開始接觸的時候對Action還會摸不著頭腦。action到底是什麼?View如何發出action。其實,action只是我們對狀態進行操作方法的一個代號而已。在我們這個應用中,唯一的一個功能就是讓count的值+1,所以我們這裡只有一個action。

/**
 * 定義操作該State的全部Action
 * 這裡只有增加count一個動作
 */
enum Action{
  increment
}
複製程式碼

第四步:建立reducer

reducer是我們的狀態生成器,它接收一個我們原來的狀態,然後接收一個action,再匹配這個action生成一個新的狀態。

/**
 * reducer會根據傳進來的action生成新的CountState
 */
CountState reducer(CountState state,action){
  //匹配Action
    if(action == Action.increment){
      return CountState(state.count+1);
    }
    return state;
}
複製程式碼

第五步:建立store

Store接收一個reducer,以及初始化State,我們想用Redux管理全域性的狀態的話,需要將store儲存在應用的入口才行。而在應用開啟時要先初始化一次應用的狀態。所以在State中新增一個初始化的函式。

//這段程式碼寫在State中
CountState.initState(){ _count = 0;}
複製程式碼
//應用頂層
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

這裡建議大家把實際程式碼對照下面的解釋一起看。

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發生變化時rebuilt Widget。

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

  • 首先這裡需要強制宣告型別,S代表我們需要從store中獲取什麼型別的state,ViewModel指的是我們使用這個State時的實際型別。
  • 然後我們需要宣告一個converter<S,ViewModel>,它的作用是將Store轉化成實際ViewModel將要使用的資訊,比如我們這裡實際上要使用的是count,所以這裡將count提取出來。
  • builder是我們實際根據state建立Widget的地方,它接收一個上下文context,以及剛才我們轉化出來的ViewModel,所以我們就只需要把拿到的count放進Text Widget中進行渲染就好了。

第七步:發出action

我們這個應用在第二個頁面中,通過點選floatingActionButton發出了action,並通知reducer生成了新的狀態。

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>。這裡由於是發出了一個動作,所以是VoidCallback。
  • store.dispatch發起一個action,任何中介軟體都會攔截該操作,在執行中介軟體後,操作將被髮送到給定的reducer生成新的狀態,並更新狀態樹。

以上便是在flutter中使用redux共享狀態資訊的全部內容。

Q&A

ViewModel效能優化

我們的StoreConnector能夠將store提取出資訊並轉化成ViewModel,這裡其實是有一個效能優化的點的。我們這裡的例子非常簡單,它的ViewModel就只是一個int的值,當我們ViewModel很複雜的時候,我們可以使用StoreConnector的distinct屬性進行效能優化。使用方法很簡單:需要我們在ViewModel中重寫[==] and [hashCode] 方法,然後把distinct屬性設為true。

如何處理非同步資料

Redux提供了一種簡單的方法來更新應用程式的狀態以響應同步操作。但是,它缺少處理非同步程式碼的工具。我們如何應對非同步相應呢。

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

你認為redux真的適合flutter嗎

我們發現,redux的確能夠在flutter中很好的工作。在react中資料是沒有上行能力的,所以通過資料單向流動形成一個環來進行狀態管理。看上去似乎並沒有把flutter中的優勢完全發揮出來。在這個簡單的例子中我們也可以看出,使用redux還是稍微有些麻煩的,用的不好,可能會陷入redux地獄。學習成本偏高也是它的一大痛點。

當然,redux這套狀態管理架構已經比較成熟,假如您已經習慣redux,也能夠快速通過flutter_redux輕鬆構建屬於您的狀態管理應用。

那麼你現在如何看待redux呢?

寫在最後

本次所用到的程式碼已經上傳Github: github.com/Vadaski/Flu…

這篇文章參考了以下資料

瞭解更多

你能在這些地方瞭解更多關於flutter-redux

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

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

相關文章