在 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;
});
}
不知看懂了没有,实际上就是利用传递函数与上下文的特点。