接上一篇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
入口路由配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| /// 创建应用的根 Widget /// 1. 创建一个简单的路由,并注册页面 /// 2. 对所需的页面进行和 AppStore 的连接 /// 3. 对所需的页面进行 AOP 的增强 Widget createApp() { final AbstractRoutes routes = PageRoutes( pages: <String, Page<Object, dynamic>>{ /// 注册TodoList主页面 'todo_list': ToDoListPage(),
}, visitor: (String path, Page<Object, dynamic> page) { /// 只有特定的范围的 Page 才需要建立和 AppStore 的连接关系 /// 满足 Page<T> ,T 是 GlobalBaseState 的子类 if (page.isTypeof<GlobalBaseState>()) { /// 建立 AppStore 驱动 PageStore 的单向数据连接 /// 1. 参数1 AppStore /// 2. 参数2 当 AppStore.state 变化时, PageStore.state 该如何变化 page.connectExtraStore<GlobalState>(GlobalStore.store, (Object pagestate, GlobalState appState) { /// 根据appState变化pagestate return pagestate; }); }
/// AOP /// 页面可以有一些私有的 AOP 的增强, 但往往会有一些 AOP 是整个应用下,所有页面都会有的。 /// 这些公共的通用 AOP ,通过遍历路由页面的形式统一加入。 page.enhancer.append( ...
/// Store AOP middleware: <Middleware<dynamic>>[ logMiddleware<dynamic>(tag: page.runtimeType.toString()), ], ); }, );
return MaterialApp( title: 'Fluro', home: routes.buildPage('todo_list', null), onGenerateRoute: (RouteSettings settings) { return MaterialPageRoute<Object>(builder: (BuildContext context) { return routes.buildPage(settings.name, settings.arguments); }); }, ); }
|
新建Page
1 2 3 4 5 6 7 8 9
| class ToDoListPage extends Page<PageState, Map<String, dynamic>> { ToDoListPage() : super( initState: initState, effect: buildEffect(), reducer: buildReducer(), view: buildView, ); }
|
- 定义state
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class PageState extends MutableSource implements GlobalBaseState, Cloneable<PageState> { List<ToDoState> 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<String, dynamic> args) { //just demo, do nothing here... return PageState(); }
|
- 定义Action
1 2 3 4 5 6 7 8 9 10 11
| enum PageAction { initToDos, onAdd }
class PageActionCreator { static Action initToDosAction(List<ToDoState> toDos) { return Action(PageAction.initToDos, payload: toDos); }
static Action onAddAction() { return const Action(PageAction.onAdd); } }
|
- 定义Reducer
1 2 3 4 5 6 7 8 9 10 11 12
| Reducer<PageState> buildReducer() { return asReducer( <Object, Reducer<PageState>>{PageAction.initToDos: _initToDosReducer}, ); }
PageState _initToDosReducer(PageState state, Action action) { final List<ToDoState> toDos = action.payload ?? <ToDoState>[]; final PageState newState = state.clone(); newState.toDos = toDos; return newState; }
|
- 定义Effect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Effect<PageState> buildEffect() { return combineEffects(<Object, Effect<PageState>>{ Lifecycle.initState: _init, PageAction.onAdd: _onAdd, }); }
void _init(Action action, Context<PageState> ctx) { final List<ToDoState> initToDos = <ToDoState>[]; /// 可作网络/IO等耗时操作 ctx.dispatch(PageActionCreator.initToDosAction(initToDos)); }
void _onAdd(Action action, Context<PageState> 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视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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: <Widget>[ ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => dispatch(PageActionCreator.onAddAction()), /// 发出意图改变状态 tooltip: 'Add', child: const Icon(Icons.add), ), ); }
|
组装子Component
- 定义state
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class ReportState implements Cloneable<ReportState> { int total; int done;
ReportState({this.total = 0, this.done = 0});
@override ReportState clone() { return ReportState() ..total = total ..done = done; }
}
|
- 定义Component
1 2 3 4 5 6
| class ReportComponent extends Component<ReportState> { ReportComponent() : super( view: buildView, ); }
|
- 定义视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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: <Widget>[ 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class ReportConnector extends ConnOp<PageState, ReportState> with ReselectMixin<PageState, ReportState> { @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如下
1 2 3 4 5 6 7 8 9 10 11 12 13
| class ToDoListPage extends Page<PageState, Map<String, dynamic>> { ToDoListPage() : super( initState: initState, effect: buildEffect(), reducer: buildReducer(), view: buildView, dependencies: Dependencies<PageState>( slots: <String, Dependent<PageState>>{ 'report': ReportConnector() + ReportComponent() }), ); }
|
- page的buildView改造如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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: <Widget>[ viewService.buildComponent('report'), /// 加载子Component ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => dispatch(PageActionCreator.onAddAction()), tooltip: 'Add', child: const Icon(Icons.add), ), ); }
|
关键对象
Middleware
StoreMiddleware,实际是对Store的dispatch函数进行加强
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| /// fish-redux-master/lib/src/redux/apply_middleware.dart StoreEnhancer<T> applyMiddleware<T>(List<Middleware<T>> middleware) { return middleware == null || middleware.isEmpty ? null : (StoreCreator<T> creator) => (T initState, Reducer<T> reducer) { final Store<T> store = creator(initState, reducer); final Dispatch initialValue = store.dispatch; /// 原始的dispatch store.dispatch = middleware .map((Middleware<T> middleware) => middleware( /// 执行middleware最外层函数返回Composable<T>,此函数在这里用于对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示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| Middleware<T> logMiddleware<T>({ String tag = 'redux', String Function(T) monitor, }) { return ({Dispatch dispatch, Get<T> 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
1 2 3 4 5 6 7 8 9 10 11 12 13
| page.connectExtraStore<GlobalState>(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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| abstract class MutableConn<T, P> implements AbstractConnector<T, P> { const MutableConn();
void set(T state, P subState);
@override SubReducer<T> subReducer(Reducer<P> 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<T>(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的实现原理
最后更新时间:
这里可以写作者留言,标签和 hexo 中所有变量及辅助函数等均可调用,示例:
http://yoursite.com/2022/03/30/Flutter状态管理之路(五)/