Flutter Redux 食用总结

目的

  1. Redux是什么
  2. Redux在Flutter里的作用
  3. 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变的很简便。

流程图

1536732607576

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))
  
复制代码

参考

github.com/johnpryan/r…

猜你喜欢

转载自juejin.im/post/5bf95aaa51882516e1542e31