内容简介:Flutter App本质上是一个单页面应用,需要我们自己维护State,Model,Route。随着业务的增加,这些工作会变得很复杂,也不可预测,复现一个bug会很困难,跨组件传递数据也很难。Redux思想继承于Flux,通过合理的约定让业务分层解耦,数据的变动可以预测,可以重现。Redux有三个原则:1.单一的数据来源(App统一的Store)2.状态State是只读的(数据不能直接修改,只能用过约定的Action触发,Reduce修改)
- Redux是什么
- Redux在Flutter里的作用
- Flutter里如何使用Redux
效果
Flutter App本质上是一个单页面应用,需要我们自己维护State,Model,Route。随着业务的增加,这些工作会变得很复杂,也不可预测,复现一个bug会很困难,跨组件传递数据也很难。Redux思想继承于Flux,通过合理的约定让业务分层解耦,数据的变动可以预测,可以重现。Redux有三个原则:
1.单一的数据来源(App统一的Store)
2.状态State是只读的(数据不能直接修改,只能用过约定的Action触发,Reduce修改)
3.数据改动须是纯函数(这些纯函数叫Reducer,定义了如何修改Store,由Action触发)
原理
Redux(3.0.0)是作者用Dart把JS 的redux库实现了,它定义了Store,Action,Reduce,Middleware以及它们之间的行为关系。
flutter_redux(0.5.2)作为 工具 类桥接Redux和Flutter,它提供了StoreProvider,StoreBuilder,StoreConnector这些组件,使我们在flutter中使用redux变的很简便。
流程图
Action
Action定义一种行为,可以携带信息,发往Store。换言之Store发生改变须由Action触发。Live Template快捷键ac,创建一套Api Aciton:
class xxxRequestAction extends VoidAction {} class xxxSuccessAction extends ActionType { final payload; xxxSuccessAction({this.payload}) : super(payload: payload); } class xxxFailureAction extends ActionType { final RequestFailureInfo errorInfo; xxxFailureAction({this.errorInfo}) : super(payload: errorInfo); } 复制代码
API
App功能最小粒度依赖是API,一般我们前后端会约定一套Rest接口定义。这里APP端是用一个静态方法封装实现的,里面定义了Path,Request,Success,Failure三个Action的响应回调。
static fetchxxx() { final access = StoreContainer.access; final apiFuture = Services.rest.get( '/zpartner_api/${access.path}/${access.businessGroupUid}/xxxx/'); Services.asyncRequest( apiFuture, xxxRequestAction(), (json) => xxxSuccessAction(payload: xxxInfo.fromJson(json)), (errorInfo) => xxxFailureAction(errorInfo: errorInfo)); } 复制代码
Reduce&state
State是Store的一个节点,定义了这个节点的数据申明,Reduce每一次响应Action都会创建一个新的State去替换原来Store里的那个节点State。Reduce和State基本上是一对一的,所以把他们放在一个文件里。Live Template快捷键rd,创建一套Reduce&State:
@immutable class xxxState { final bool isLoading; xxxState({this.isLoading}); xxxState copyWith({bool isLoading}) { return xxxState(isLoading: isLoading ?? this.isLoading); } xxxState.initialState() : isLoading = false; } class xxxReducer { xxxState reducer(xxxState state, ActionType action) { switch (action.runtimeType) { case xxxRequestAction: return state.copyWith(isLoading: ); case xxxSuccessAction: return state.copyWith(isLoading: ); case xxxFailureAction: return state.copyWith(isLoading: ); default: return state; } } } 复制代码
Middleware
中间件,插在Action触发后还没有到达Reduce之间执行,一般是用来做一些API异步请求并处理。这一步是可选的,当时鉴于Dio网络库对数据有Json处理,flutter_epic表现也还不够稳定。所以我们没用Middleware而是封装了一个工具方法在API services里直接调用处理API并且根据结果分发对应Action。有接入集成测试的需要,需要重点考虑是否引入它。
进阶
全局Action
App里的登出操作是比较特殊的,它可能在不同模块被调起,而且需要做的操作是清空整个Store。我们用了一个GlobalReduce去分发Action
AppState reduxReducer(AppState state, action) => GlobalReducer().reducer(state, action); class GlobalReducer { AppState reducer(AppState state, ActionType action) { switch (action.runtimeType) { case AppRestartAction: hasToken(); return _initialReduxState(); default: return AppState( login: LoginReducer().reducer(state.login, action), ...) } } } 复制代码
APIFuction
前面提到我们没有使用Middleware,而是自己封装了一个工具Function,好处是简单易用,缺点是没有明确返回值不好写测试,利弊需要权衡下的。
/// common function for network with dio /// Future<Response> apiFuture [Dio.request] /// request action /// success action /// failure action static asyncRequest( Future<Response> apiFuture, ActionType request, ActionType Function(dynamic) success, ActionType Function(RequestFailureInfo) failure, ) async { // request StoreContainer.global.dispatch(request); final requestBegin = DateTimeUtil.dateTimeNowMilli(); try { final response = await apiFuture; final requestEnd = DateTimeUtil.dateTimeNowMilli(); final requestSpend = requestEnd - requestBegin; if (requestSpend < requestMinThreshold) { await Future.delayed(Duration( milliseconds: requestMinThreshold - requestSpend)); // 请求返回太快,页面有点卡顿,有点尴尬 todo } // success StoreContainer.global.dispatch(success(response.data)); } on DioError catch (error) { var message = ''; var code = '-1'; var url = ''; if (error.response != null) { var errorData = error.response.data; List messageList = errorData is Map<String, dynamic> ? ((errorData['message']) ?? []) : []; messageList .forEach((item) => message = message + item.toString() + ' '); code = error.response.statusCode.toString(); url = error.response.request.baseUrl + error.response.request.path; } else { message = error.message; } final model = RequestFailureInfo( errorCode: code, errorMessage: message, dateTime: DateTimeUtil.dateTimeNowIso()); // failure StoreContainer.global.dispatch(failure(model)); } } 复制代码
局部刷新
使用flutter_redux提供的StoreConnector组件时,可以设置distinct为ture,Store变化后是否刷新视图可以完全自己控制。原理是需要重载ViewModel的==运算符和重写hashcode方法。这样在Store变化时,StoreStreamListener通过比对前后两个ViewModel是否相等来触发是否重新builder,而这个是否相等都是我们重写并自己控制的。
class _RestartAppViewModel { Key key; bool isLogin; _RestartAppViewModel({this.key, this.isLogin}); static _RestartAppViewModel fromStore(Store<AppState> store) => _RestartAppViewModel( key: store.state.cache.key, isLogin: store.state.cache.isLogin); @override int get hashCode => key.hashCode ^ isLogin.hashCode; @override bool operator ==(other) => identical(this, other) || other is _RestartAppViewModel && key == other.key && isLogin == other.isLogin; } StoreConnector<AppState, _RestartAppViewModel>( distinct: true, builder: (context, vm) { return App(vm.isLogin, vm.key); }, converter: (store) => _RestartAppViewModel.fromStore(store)) 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Emmet 食用指北
- Echarts 的食用方式
- 拿走不谢,这份【亿级流量系统】数据一致性重构的食用指南【石杉的架构笔记】
- Reddit大热,伯克利PPT带你丝滑入门机器学习:知识点全面覆盖,笔记可搭配食用
- 【Vue项目总结】后台管理项目总结
- JavaScript基础总结(四)——字符串总结
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。