Flutter Redux 食用總結

目的

  1. Redux是什麼
  2. Redux在Flutter裏的做用
  3. Flutter裏如何使用Redux

效果

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

流程圖

1536732607576

Action

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);
}
複製代碼

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。有接入集成測試的須要,須要重點考慮是否引入它。async

進階

全局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/hyjfine/flu…

參考

github.com/johnpryan/r…

(完)

@子路宇, 本文版權屬於再惠研發團隊,歡迎轉載,轉載請保留出處。

相關文章
相關標籤/搜索