Flutter App本质上是一个单页面应用,须要咱们本身维护State,Model,Route。随着业务的增长,这些工做会变得很复杂,也不可预测,复现一个bug会很困难,跨组件传递数据也很难。Redux思想继承于Flux,经过合理的约定让业务分层解耦,数据的变更能够预测,能够重现。Redux有三个原则:git
1.单一的数据来源(App统一的Store)github
2.状态State是只读的(数据不能直接修改,只能用过约定的Action触发,Reduce修改)json
3.数据改动须是纯函数(这些纯函数叫Reducer,定义了如何修改Store,由Action触发)redux
Redux(3.0.0)是做者用Dart把JS 的redux库实现了,它定义了Store,Action,Reduce,Middleware以及它们之间的行为关系。后端
flutter_redux(0.5.2)做为工具类桥接Redux和Flutter,它提供了StoreProvider,StoreBuilder,StoreConnector这些组件,使咱们在flutter中使用redux变的很简便。api
Action定义一种行为,能够携带信息,发往Store。换言之Store发生改变须由Action触发。Live Template快捷键ac,建立一套Api Aciton:markdown
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);
}
复制代码
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));
}
复制代码
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;
}
}
}
复制代码
中间件,插在Action触发后尚未到达Reduce之间执行,通常是用来作一些API异步请求并处理。这一步是可选的,当时鉴于Dio网络库对数据有Json处理,flutter_epic表现也还不够稳定。因此咱们没用Middleware而是封装了一个工具方法在API services里直接调用处理API而且根据结果分发对应Action。有接入集成测试的须要,须要重点考虑是否引入它。async
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),
...)
}
}
}
复制代码
前面提到咱们没有使用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))
复制代码
(完)
@子路宇, 本文版权属于再惠研发团队,欢迎转载,转载请保留出处。