在 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;
});
}
不知看懂了沒有,實際上就是利用傳遞函數與上下文的特點。