Flutter入門與實戰(五十八): 看 Flutter 如何分享 React 的 Redux狀態管理

島上碼農發表於2021-08-21

這是我參與8月更文挑戰的第21天,活動詳情檢視:8月更文挑戰

前言

React 開發者都知道Redux 在開發大中型 Web 應用的重要性, Flutter 作為 React 的高仿者,連 setState 都搬過來了,自然也需要把 Redux 也搬過來——好東西要一起分享

image.png

Redux 簡介

Redux 是一個單向資料流的狀態管理架構,通過它能夠簡化應用開發、測試和維護。Flutter的 Redux 相關外掛地址:

  • redux:Flutter 中需要的全部 Redux 元件,例如 StoreReducerMiddleware
  • flutter_redux:Flutter 專屬的外掛包,在 redux 基礎上進行了封裝,使得在 Flutter 中應用更為簡便,例如 StoreProvider(為元件提供 Store的基礎 Widget),StoreBuilder(從 StoreProvider 中接收 Store 的元件)和 StoreConnector(用於替換 StoreBuilder,可以將 Store 轉換為一個 ViewModel 來構建元件樹,一旦 Store 中的 State 改變了就可以重建 StoreConnector)。

Redux 中,通過 Store 儲存了代表整個應用的狀態物件 State。每一個應用事件(不管是使用者發起的或外部驅動)都被認為是一個 ActionAction 通過 Reducer 進行排程,然後 Reducer 根據 Action 來更新 Store 中的狀態。而一旦 Store 中的狀態物件 State改變了,對應的檢視 View 就會反映狀態的變化。 image.png Redux 的最大特點就是可以將大部分元件進行解耦,使得 UI介面的重新整理更容易。而且,核心的業務邏輯都在 Reducer 函式中。Reducer 接收一個 Action和當前的狀態,然後返回一個新的狀態物件。因此,使得單元測試更容易,我們只需要測試 Reducer 就可以測試狀態業務邏輯對不對。

Redux 中介軟體

上面僅僅是 Redux 的第一印象,但是如果要進行非同步操作,例如從介面載入資料怎麼辦?這個時候就需要引入新的元件,稱之為中介軟體(Middleware)。

中介軟體就是在Reducer前處理 Action 的元件。它接收當前的狀態物件和要被排程的Action,然後執行程式碼(如介面通訊,資料訪問等)。最終,中介軟體可能繼續使用原先的 Action,或一個不同的 Action,甚至什麼都不做。有了中介軟體後,Redux 的流程圖就變成了下面這樣:

image.png

上程式碼

接下來以一個簡單的記數應用為例,來演示 Redux 的基本應用。首先我們構建Redux 的三要素。

// counter_actions.dart,目前為空,後續可以增加業務程式碼
// 計數器增加
class CounterAddAction {}
//計數器減少
class CounterSubAction {}

//counter_state.dart
class CounterState {
  int count = 0;

  CounterState(this.count);
}

// counter_reducer.dart
CounterState counterReducer(CounterState state, dynamic action) {
  if (action.runtimeType == CounterAddAction) {
    return CounterState(state.count + 1);
  }

  if (action.runtimeType == CounterSubAction) {
    return CounterState(state.count - 1);
  }

  return state;
}

複製程式碼

可以看到,核心業務都在 reducer 裡面,reducer 根據 action 的型別更新state,然後返回新的state 物件。action 是一個動態型別,如果簡單的操作也可以是列舉,但是如果要攜帶新的資料或業務邏輯(例如,我們可以每次點選可以改變增加或減少的數量)使用物件會更方便。

ReduxReducer 定義形式如下,即接收舊的 stateaction 引數,然後返回新的 state

typedef Reducer<State> = State Function(State state, dynamic action)
複製程式碼

接下來是頁面邏輯的實現了。頁面邏輯有點類似。如果一個 Store 需要被下級元件使用,那麼就要定義在上一級元件中。這裡我們比較簡單,直接定義在當前頁面即可。關鍵的部分有以下部分:

  • build 的子元件使用 StoreProvider 包裹,以便共享 store
  • 需要監聽store 變化的元件使用 StoreConnectorbuilder 構建,並且可以使用 converter 方法將 store 轉成元件需要的資料(即將狀態資料轉成 ViewModel),這裡只是簡單地將計數值轉為了 String。在 StoreConnector 方法中構建的元件,一旦狀態物件發生改變後就會通知重新整理。
  • 按鈕事件:在按鈕中執行狀態物件的dispatch操作,以便排程對應的 reducer 完成狀態更新。dispatch 方法定義如下,可以看到實際上 dispatch 是一組排程,這裡只是呼叫了第一個方法,這意味著其實可以執行一系列的排程操作。
dynamic dispatch(dynamic action) {
  return _dispatchers[0](action);
}
複製程式碼
class CounterPage extends StatelessWidget {
  CounterPage({Key? key}) : super(key: key);
  final store =
      Store<CounterState>(counterReducer, initialState: CounterState(0));
  @override
  Widget build(BuildContext context) {
    return StoreProvider(
      store: store,
      child: Scaffold(
        appBar: AppBar(
          title: Text('Redux Counter'),
        ),
        body: Center(
          child: StoreConnector<CounterState, String>(
            converter: (store) => store.state.count.toString(),
            builder: (context, count) {
              return Text(
                count,
                style: TextStyle(color: Colors.blue, fontSize: 30),
              );
            },
          ),
        ),
        floatingActionButton: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            FloatingActionButton(
                child: Icon(Icons.arrow_upward),
                onPressed: () {
                  store.dispatch(CounterAddAction());
                }),
            FloatingActionButton(
                backgroundColor: Colors.red,
                child: Icon(
                  Icons.arrow_downward,
                ),
                onPressed: () {
                  store.dispatch(CounterSubAction());
                }),
          ],
        ),
      ),
    );
  }
}
複製程式碼

執行效果

執行效果如下所示。 螢幕錄製2021-08-21 下午1.59.54.gif

總結

本篇介紹了 Redux 的基本概念和一個簡單的示例,通常來說,使用Redux 的一大好處是整個狀態管理的業務模式都是一致的,都必須具備 ActionStoreReducer 這三個環節,這樣其實也能夠間接規範程式碼結構。同時,有了 flutter_redux 外掛的適配,在 Flutter 中使用Redux也更為簡便。


我是島上碼農,微信公眾號同名,這是Flutter 入門與實戰的專欄文章,對應原始碼請看這裡:Flutter 入門與實戰專欄原始碼

??:覺得有收穫請點個贊鼓勵一下!

?:收藏文章,方便回看哦!

?:評論交流,互相進步!

相關文章