Flutter状态管理之路(五)

接上一篇Flutter状态管理之路(四)
此篇主要介绍flutter_mobx

Fish Redux

版本:0.2.7

库地址:https://github.com/alibaba/fish-redux/

演进过程

闲鱼fish_redux演进过程1.png
闲鱼fish_redux演进过程2.png

概念

对象 说明 所属库
Action 表示一种意图,包含两个字段
type,payload
Connector 表达了如何从一个大数据中读取小数据,
同时对小数据的修改如何同步给大数据,这样的数据连接关系
Reducer 一个上下文无关的 pure function
State 状态值
Middleware 中间件,以AOP面向切面形式注入逻辑
Component 对视图展现和逻辑功能的封装
Effect 处理Action的副作用
Dependent 表达了小组件|小适配器是如何连接到 大的Component 的
Page 继承Component,针对页面级的抽象,内置一个Store(子Component共享)

使用

例子来源官方 Todos

  1. 入口路由配置

    /// 创建应用的根 Widget
    /// 1. 创建一个简单的路由,并注册页面
    /// 2. 对所需的页面进行和 AppStore 的连接
    /// 3. 对所需的页面进行 AOP 的增强
    Widget createApp() {
      final AbstractRoutes routes = PageRoutes(
        pages: >{
          /// 注册TodoList主页面
          'todo_list': ToDoListPage(),
    
        },
        visitor: (String path, Page page) {
          /// 只有特定的范围的 Page 才需要建立和 AppStore 的连接关系
          /// 满足 Page ,T 是 GlobalBaseState 的子类
          if (page.isTypeof()) {
            /// 建立 AppStore 驱动 PageStore 的单向数据连接
            /// 1. 参数1 AppStore
            /// 2. 参数2 当 AppStore.state 变化时, PageStore.state 该如何变化
            page.connectExtraStore(GlobalStore.store,
                (Object pagestate, GlobalState appState) {
              /// 根据appState变化pagestate
              return pagestate;
            });
          }
    
          /// AOP
          /// 页面可以有一些私有的 AOP 的增强, 但往往会有一些 AOP 是整个应用下,所有页面都会有的。
          /// 这些公共的通用 AOP ,通过遍历路由页面的形式统一加入。
          page.enhancer.append(
            ...
    
            /// Store AOP
            middleware: >[
              logMiddleware(tag: page.runtimeType.toString()),
            ],
          );
        },
      );
    
      return MaterialApp(
        title: 'Fluro',
        home: routes.buildPage('todo_list', null),
        onGenerateRoute: (RouteSettings settings) {
          return MaterialPageRoute(builder: (BuildContext context) {
            return routes.buildPage(settings.name, settings.arguments);
          });
        },
      );
    }
      
      
  2. 新建Page

  3. class ToDoListPage extends Page> {
      ToDoListPage()
          : super(
              initState: initState,
              effect: buildEffect(),
              reducer: buildReducer(),
              view: buildView,
            );
    }
    
    1. 定义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();
    }
    
    1. 定义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);
      }
    }
    
    1. 定义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;
    }
    
    
    1. 定义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));
        }
      });
    }
    
    1. 定义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

    1. 定义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;
      }
    
    }
    
    
    1. 定义Component
    class ReportComponent extends Component {
      ReportComponent()
          : super(
              view: buildView,
            );
    }
    
    1. 定义视图
    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),
              )
            ],
          ));
    }
    
    1. 定义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');
      }
    }
    
    1. page中使用,改造page如下
    class ToDoListPage extends Page> {
      ToDoListPage()
          : super(
              initState: initState,
              effect: buildEffect(),
              reducer: buildReducer(),
              view: buildView,
              dependencies: Dependencies(
                  slots: >{
                    'report': ReportConnector() + ReportComponent()
                  }),
            );
    }
    
    1. 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函数进行加强

    store_middleware.png
    /// 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

    fish_redux_全局store时序图.png
     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

    fish_redux_connector_reducer.png
    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

    总结

    优点:

    1. 每个Page一个Store,子Component共享其Store,单个Component仍拥有redux的特性以实现分治
    2. 子的reducer自动合并,与page的store自动进行数据同步
    3. 利用eventbus 建立page之间的联系,通过broadcast effect来分发page自身不关心的Action给其它page
    4. 可以全局共享状态,定义一个全局Store在用page.connectExtraStore关联

    缺点:

    1. 概念较多,学习曲线较高
    2. 需要定义的各类对象多、文件多
    3. 对项目规模把握不到位容易引入不必要的复杂度
    4. 代码结构侵入性较大

    未完待续

    fish_redux框架定义的概念很多,还需要继续深入...

    参考

    1. 手把手入门Fish-Redux开发flutter
    2. Connector的实现原理

    你可能感兴趣的:(Flutter状态管理之路(五))