接上一篇Flutter状态管理之路(四)
此篇主要介绍flutter_mobx
Fish Redux
版本:0.2.7
库地址:https://github.com/alibaba/fish-redux/
演进过程
概念
对象 | 说明 | 所属库 |
---|---|---|
Action | 表示一种意图,包含两个字段 type,payload |
|
Connector | 表达了如何从一个大数据中读取小数据, 同时对小数据的修改如何同步给大数据,这样的数据连接关系 |
|
Reducer | 一个上下文无关的 pure function | |
State | 状态值 | |
Middleware | 中间件,以AOP面向切面形式注入逻辑 | |
Component | 对视图展现和逻辑功能的封装 | |
Effect | 处理Action的副作用 | |
Dependent | 表达了小组件|小适配器是如何连接到 大的Component 的 | |
Page | 继承Component,针对页面级的抽象,内置一个Store(子Component共享) |
使用
例子来源官方 Todos
-
入口路由配置
/// 创建应用的根 Widget /// 1. 创建一个简单的路由,并注册页面 /// 2. 对所需的页面进行和 AppStore 的连接 /// 3. 对所需的页面进行 AOP 的增强 Widget createApp() { final AbstractRoutes routes = PageRoutes( pages:
>{ /// 注册TodoList主页面 'todo_list': ToDoListPage(), }, visitor: (String path, Page 新建Page
class ToDoListPage extends Page> {
ToDoListPage()
: super(
initState: initState,
effect: buildEffect(),
reducer: buildReducer(),
view: buildView,
);
}
- 定义state
class PageState extends MutableSource
implements GlobalBaseState, Cloneable {
List toDos;
@override
Color themeColor;
@override
PageState clone() {
return PageState()
..toDos = toDos
..themeColor = themeColor;
}
@override
Object getItemData(int index) => toDos[index];
@override
String getItemType(int index) => 'toDo';
@override
int get itemCount => toDos?.length ?? 0;
@override
void setItemData(int index, Object data) => toDos[index] = data;
}
PageState initState(Map args) {
//just demo, do nothing here...
return PageState();
}
- 定义Action
enum PageAction { initToDos, onAdd }
class PageActionCreator {
static Action initToDosAction(List toDos) {
return Action(PageAction.initToDos, payload: toDos);
}
static Action onAddAction() {
return const Action(PageAction.onAdd);
}
}
- 定义Reducer
Reducer buildReducer() {
return asReducer(
>{PageAction.initToDos: _initToDosReducer},
);
}
PageState _initToDosReducer(PageState state, Action action) {
final List toDos = action.payload ?? [];
final PageState newState = state.clone();
newState.toDos = toDos;
return newState;
}
- 定义Effect
Effect buildEffect() {
return combineEffects(>{
Lifecycle.initState: _init,
PageAction.onAdd: _onAdd,
});
}
void _init(Action action, Context ctx) {
final List initToDos = [];
/// 可作网络/IO等耗时操作
ctx.dispatch(PageActionCreator.initToDosAction(initToDos));
}
void _onAdd(Action action, Context ctx) {
Navigator.of(ctx.context)
.pushNamed('todo_edit', arguments: null)
.then((dynamic toDo) {
if (toDo != null &&
(toDo.title?.isNotEmpty == true || toDo.desc?.isNotEmpty == true)) {
ctx.dispatch(list_action.ToDoListActionCreator.add(toDo));
}
});
}
- 定义View视图
Widget buildView(PageState state, Dispatch dispatch, ViewService viewService) {
return Scaffold(
appBar: AppBar(
backgroundColor: state.themeColor, /// 获取state状态
title: const Text('ToDoList'),
),
body: Container(
child: Column(
children: [
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => dispatch(PageActionCreator.onAddAction()), /// 发出意图改变状态
tooltip: 'Add',
child: const Icon(Icons.add),
),
);
}
组装子Component
- 定义state
class ReportState implements Cloneable {
int total;
int done;
ReportState({this.total = 0, this.done = 0});
@override
ReportState clone() {
return ReportState()
..total = total
..done = done;
}
}
- 定义Component
class ReportComponent extends Component {
ReportComponent()
: super(
view: buildView,
);
}
- 定义视图
Widget buildView(
ReportState state,
Dispatch dispatch,
ViewService viewService,
) {
return Container(
margin: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(8.0),
color: Colors.blue,
child: Row(
children: [
Container(
child: const Icon(Icons.report),
margin: const EdgeInsets.only(right: 8.0),
),
Text(
'Total ${state.total} tasks, ${state.done} done.',
style: const TextStyle(fontSize: 18.0, color: Colors.white),
)
],
));
}
- 定义Connector来连接父子Component
class ReportConnector extends ConnOp
with ReselectMixin {
@override
ReportState computed(PageState state) {
return ReportState()
..done = state.toDos.where((ToDoState tds) => tds.isDone).length
..total = state.toDos.length;
}
@override
void set(PageState state, ReportState subState) {
throw Exception('Unexcepted to set PageState from ReportState');
}
}
- page中使用,改造page如下
class ToDoListPage extends Page> {
ToDoListPage()
: super(
initState: initState,
effect: buildEffect(),
reducer: buildReducer(),
view: buildView,
dependencies: Dependencies(
slots: >{
'report': ReportConnector() + ReportComponent()
}),
);
}
- page的buildView改造如下
Widget buildView(PageState state, Dispatch dispatch, ViewService viewService) {
final ListAdapter adapter = viewService.buildAdapter();
return Scaffold(
appBar: AppBar(
backgroundColor: state.themeColor,
title: const Text('ToDoList'),
),
body: Container(
child: Column(
children: [
viewService.buildComponent('report'), /// 加载子Component
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => dispatch(PageActionCreator.onAddAction()),
tooltip: 'Add',
child: const Icon(Icons.add),
),
);
}
关键对象
Middleware
StoreMiddleware,实际是对Store的dispatch函数进行加强
/// fish-redux-master/lib/src/redux/apply_middleware.dart
StoreEnhancer applyMiddleware(List> middleware) {
return middleware == null || middleware.isEmpty
? null
: (StoreCreator creator) => (T initState, Reducer reducer) {
final Store store = creator(initState, reducer);
final Dispatch initialValue = store.dispatch; /// 原始的dispatch
store.dispatch = middleware
.map((Middleware middleware) => middleware( /// 执行middleware最外层函数返回Composable,此函数在这里用于对dispatch包装
dispatch: (Action action) => store.dispatch(action),
getState: store.getState,
))
.fold(
initialValue,
(Dispatch previousValue,
Dispatch Function(Dispatch) element) =>
element(previousValue), /// 每次将上一个dispatch传入,返回一个新的dispatch,利用闭包,新的dispatch持有了上一个dispatch的引用
);
return store;
};
}
其中某一个Middleware示例如下:
Middleware logMiddleware({
String tag = 'redux',
String Function(T) monitor,
}) {
return ({Dispatch dispatch, Get getState}) {
return (Dispatch next) { /// 此方法在上一个示意代码段里,是fold方法里的element
return isDebug()
? (Action action) { /// 返回包装的Dispatch
print('---------- [$tag] ----------');
print('[$tag] ${action.type} ${action.payload}');
final T prevState = getState();
if (monitor != null) {
print('[$tag] prev-state: ${monitor(prevState)}');
}
next(action);
final T nextState = getState();
if (monitor != null) {
print('[$tag] next-state: ${monitor(nextState)}');
}
}
: next;
};
};
}
全局Store
page.connectExtraStore(GlobalStore.store,
(Object pagestate, GlobalState appState) {
final GlobalBaseState p = pagestate;
if (p.themeColor != appState.themeColor) {
if (pagestate is Cloneable) {
final Object copy = pagestate.clone();
final GlobalBaseState newState = copy;
newState.themeColor = appState.themeColor;
return newState;
}
}
return pagestate;
});
Connector
abstract class MutableConn implements AbstractConnector {
const MutableConn();
void set(T state, P subState);
@override
SubReducer subReducer(Reducer reducer) {
/// 将本Component的reducer包装成新的reducer给父的store注入
return (T state, Action action, bool isStateCopied) {
final P props = get(state);
if (props == null) {
return state;
}
final P newProps = reducer(props, action); /// 调用本Component的reducer,返回子的state
final bool hasChanged = newProps != props;
final T copy = (hasChanged && !isStateCopied) ? _clone(state) : state;
if (hasChanged) {
set(copy, newProps); /// 通知父Component同步状态
}
return copy;
};
}
}
其余详见官方文档:https://github.com/alibaba/fish-redux/blob/master/doc/README-cn.md
总结
优点:
- 每个Page一个Store,子Component共享其Store,单个Component仍拥有redux的特性以实现分治
- 子的reducer自动合并,与page的store自动进行数据同步
- 利用eventbus 建立page之间的联系,通过broadcast effect来分发page自身不关心的Action给其它page
- 可以全局共享状态,定义一个全局Store在用page.connectExtraStore关联
缺点:
- 概念较多,学习曲线较高
- 需要定义的各类对象多、文件多
- 对项目规模把握不到位容易引入不必要的复杂度
- 代码结构侵入性较大
未完待续
fish_redux框架定义的概念很多,还需要继续深入...
参考
- 手把手入门Fish-Redux开发flutter
- Connector的实现原理