Flutter 狀態管理

在 React Native 要做狀態管理通常都是使用 Redux,這種方式非常好。幸運的是,在 Flutter 也支持使用 Redux。

flutter_redux 是一個支持在 Flutter 里使用 Redux 的庫,但并不是使用 JS 版本的 Redux,而是使用 dart_redux,這是一個在 dart 上的 Redux 實現。

Redux 的基礎知識就不多說了,在這里主要介紹如何在 Flutter 里使用 Redux 管理應用的數據狀態。

先安裝依賴:

dependencies:
  redux: ^3.0.0
  flutter_redux: ^0.5.2

在 Flutter Redux 里有一個必要知道的概念:

  • StoreProvider - 一個組件,它會將給定的 Redux Store 傳遞給它的所有子組件。
  • StoreBuilder - 一個子 Widget 組件,StoreProvider 它從 Action 中獲取 Store 并將其傳遞給 Widget builder 函數。
  • StoreConnector - 從最近的 StoreProvider 祖先獲取 Store 的子 Widget,使用給定的函數將其轉換 Store 為 Action,并將其傳遞給函數。只要 Store 發出更改事件,Widget 就會自動重建。無需管理訂閱!

Flutter Redux 的概念就像是 react-redux 一樣。

下面來看一個簡單的示例。

import 'package:flutter/material.dart';
import 'package:redux/redux.dart';
import 'package:flutter_redux/flutter_redux.dart';

void main() {
    runApp(new FlutterReduxApp());
}

// 定義個 Types 枚舉
enum Types { Increment }

// Reducer 處理
int Reducer(int state, action) {
    if (action == Types.Increment) {
        return state + 1;
    }

    return state;
}

// 創建一個 Action 類
class Action {
    dynamic store;
    dynamic dispatch;
    Action(store) {
        this.store = store;
        this.dispatch = store.dispatch;
    }

    // 某個 Action
    increment() {
        dispatch(Types.Increment);
    }
}

class FlutterReduxApp extends StatelessWidget {
    // 創建一個 store
    Action action;
    final store = new Store(Reducer, initialState: 0);

    @override
    Widget build(BuildContext context) {
        // 初始化 action
        if (action == null) {
            action = new Action(store);
        }

        return new MaterialApp(
            // 創建一個 Provider
            home: new StoreProvider(
                store: this.store,
                child: new Scaffold(
                    body: new Center(
                        child: new Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                                new Text('You have pushed the button this many times:'),
                                // 連接器,連接之后可以獲取數據,在 builder 里渲染內容
                                new StoreConnector<int, String>(
                                    converter: (store) => store.state.toString(),
                                    builder: (context, count) => new Text(count),
                                )
                            ],
                        ),
                    ),
                    floatingActionButton: new FloatingActionButton(
                        onPressed: action.increment, // action
                        child: new Icon(Icons.add),
                    ),
                ),
            ),
        );
    }
}

在需要改變狀態的組件里使用 StoreConnector 連接數據(避免對整個內容做一次性的連接,導致整個渲染)。

在這里 Store 是一個泛型類,在初始化時請給它一個類型,否則就是 Store<int>。很多時候 State 是一個復雜的結構,并不是一個簡單的 int 類型,那么如何適配?

// Reducer 處理
State reducer(State state, action) {
    if (action == Types.Increment) {
        state.count++;
        return state.newState(state);
    }

    return state;
}

class State {
    List<String> list;
    String name;
    int count;
    State(this.list, this.name, this.count);
    // 返回一個新的實例
    newState(State obj) {
        this.name = obj.name;
        this.list = obj.list;
        this.count = obj.count;
        return new State(obj.list, obj.name, obj.count);
    }
}

// 創建一個 store
final store = new Store<State>(reducer, initialState: new State([], 'abc', 0));

// 連接器,連接之后可以獲取數據,在 builder 里渲染內容
new StoreConnector<State, String>(
    converter: (store) => store.state.count.toString(),
    builder: (context, count) => new Text(count),
),

StoreConnector 里的 converter 一定要是返回 String 嗎?目前為止是的,因為大多數的在 Widget 上都是使用字符串渲染。

此外,可以使用 StoreConnector 對某個組件進行數據的連接,對于不變化的組件那就沒必要連接了。

在 Action 初始了一個 store 和 dispatch,這樣就可以在方法里直接引用 store 和 dispatch。

// 創建一個 Action 類
class Action {
    Store<State> store;
    dynamic dispatch;
    Action(store) {
        this.store = store;
        this.dispatch = store.dispatch;
    }

    // 某個 Action
    increment() {
        dispatch(Types.Increment);
    }
}

中間件

中間件是一個非常有用的東西,它可以在 Reducer 操作之前做一些攔截或者記錄工作。

Store 的第三個參數是一個接受中間件的參數,我們可以通過下面的定義中間件方式,為 Store 添加中間件。

loggingMiddleware<T>(Store<T> store, action, NextDispatcher next) {
  print('${new DateTime.now()}: $action');
  next(action);
}

final store = new Store<int>(
    counterReducer,
    initialState: 0,
    middleware: [loggingMiddleware],
);

異步操作

在 action 里使用 async 即可,在等待異步操作完成后再調用 dispatch。

// 某個 Action
increment() async {
    // 模擬某個異步操作
    await new Future.delayed(new Duration(seconds: 1));
    // 完成后 dispatch
    dispatch(Types.Increment);
}

傳遞數據

在調用 dispatch 的時候 action 是一個 dynamic 類型,在上面是沒有傳遞數據的,而數據的變化在 Reducer 里。

那么如何傳遞數據呢?在 action 里是一個 dynamic 類型,因此可以把 action 寫成 Map 類型,在 Map 里獲取 type 和 data。

// Reducer 處理
State reducer(State state, action) {
    if (action['type'] == Types.Increment) {
        state.count = action['data'];
        return state.newState(state);
    }

    return state;
}

// 某個 Action
increment() async {
    // 模擬某個異步操作
    await new Future.delayed(new Duration(seconds: 1));
    // 完成后 dispatch
    dispatch({
      'type': Types.Increment,
      'data': store.state.count + 1,
    });
}

這樣就可以把數據從 Action 傳遞到 Reducer 了。

精簡代碼

在上面的代碼里,在 Action 里得到最新的狀態只,在 Reducer 里又需要進行一次賦值,這樣會多出很多重復的代碼。現在把 Action 變成一個 newState 函數,在函數里更新最新的狀態,在 Reducer 里只創建新的實例即可,這樣可以節省很多代碼,Types 也不需要寫了。

// Reducer 處理
State reducer(State state, action) {
    if (action is Function) {
        var s = state.newState(action(state));
        if (s != null) return s;
    }
    return state;
}

// 某個 Action
increment() async {
    // 模擬某個異步操作
    await new Future.delayed(new Duration(seconds: 1));
    // 完成后 dispatch

    dispatch((State state) {     // 要更新的狀態
        state.count++;
        return state;
    });
}

不知看懂了沒有,實際上就是利用傳遞函數與上下文的特點。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容