fish_redux 使用指北(這多是比官方更友好的文檔)

注意:

閱讀這遍文章須要具有如下知識:redux

  • 瞭解 Flutter,最好寫過 demo
  • 瞭解 fish_redux 的基本概念

1、 fish_redux 構建項目順序與項目目錄

  • 項目構建步驟:
  1. 配置路由
  2. 使用 Page 構建頁面,這裏面能夠配置 state、effect、reducer 等要素
  3. 定義全局 state
  4. 定義 effect 、 middleware 、reducer 用於實現反作用、中間件、結果返回處理等
  5. 定義 view 用於繪製頁面
  6. 使用 dependencies 聲明頁面須要用的適配器(adapter)、插槽(slots)
  7. 用 Connector(繼承自 ConnOp),將 store 的數據分配到 component
  • 項目目錄:

這是我我的的項目結構,僅供參考。有些與官方的 example 不太同樣。例如官方 todoList 的 demo 中是把 connector 與 state 寫在一塊的,我這裏將 connector 單獨抽出了一個文件。api

2、關鍵 API

Route

fish_redux 有一套本身的路由,有三種 路由方式數組

  • AppRoutes:多個 page 共享一個 store
  • PageRoutes: 頁面級別的路由,每一個頁面都有一個 store
  • HybridRoutes:能夠結合上面兩種 route 方式

AppRoutes

AppRoutes 包含了 State, Reducer, pages && connector,能夠將 store 中的數據 分發到各個頁面。其組裝方式跟 Page 差很少哦。bash

例:app

final AbstractRoutes appRoutes = AppRoutes<AppState>(
    preloadedState: AppState.initState(),
    pages: {
      'homePage': HomePageConnector() + HomePage(),
    },
    reducer: buildReducer()
  );

  return MaterialApp(
      home: appRoutes.buildPage('homePage', null),
      onGenerateRoute: (RouteSettings settings) {
      return MaterialPageRoute<Object>(builder: (BuildContext context) {
        return appRoutes.buildPage(settings.name, settings.arguments);
      });
    },
  );
  
  /// 想要使用 AppRoutes 發起一個 action,須要將它保存起來,使用它的 store
  /// 像下面這樣
  appRoutes.store.dispatch(AppActionCreator.someEvent());
複製代碼
  • 使用 AppRoutes 建立了一個路由 -> appRoutes
  • 使用 preloadedState 屬性初始化 state
  • 裏面定義了 homePage 頁面,HomePageConnector 中將某些 state 分配給 homePage。

PageRoutes

PageRoutes 使用起來就比較簡單,由於其不須要定義與分配 state,每一個頁面的 store 在其自身的 Page 裏定義。async

例:ide

final AbstractRoutes pageRoutes = PageRoutes(
      pages: <String, Page<Object, dynamic>>{
        'homePage': HomePage(),
      },
  );

  return MaterialApp(
      home: pageRoutes.buildPage('homePage', null),
      onGenerateRoute: (RouteSettings settings) {
      return MaterialPageRoute<Object>(builder: (BuildContext context) {
        return pageRoutes.buildPage(settings.name, settings.arguments);
      });
    },
  );
複製代碼

HybridRoutes

HybridRoutes 接受一個 route 數組,數組裏既能夠接受 AppRoutes 也能夠接受 PageRoutes函數

例:性能

final AbstractRoutes hybridRoutes = HybridRoutes(routes: <AbstractRoutes>[
    appRoutes,
    pageRoutes
  ]);
複製代碼

狀態管理(redux)

Store

這個store是我本身的叫法,類比於 redux 的 store,定義全局的 state.fetch

一、 定義 store 數據

使用 class 來聲明全局 state,在 聲明 page 的時候注入

例:

class PageState implements Cloneable <PageState> {
  String someState;
  @override
  PageState clone() {
    return PageState()
      ..someState = someState
  }
}
複製代碼
  • 上面有個 clone 方法,用來獲取 state,在 reducer 中會用到用來 merge state。
  • 須要注意的是,state 必須 繼承自 Cloneable,不然註冊不上 reducer
  • 若是沒有特殊場景須要,在 clone 中要把全部的 state 寫全,否則當在 reducer 中使用 clone 方法進行 merge 數據的時候,會丟掉裏面缺乏的數據。
  1. 使用 Connect 給組件分配 state

類比於 redux 的 connect

class HeaderConnector extends ConnOp<PageState, HeaderState> {
  @override
  HeaderState get(PageState state) {
    final HeaderState headerState = HeaderState(
      someState: state.someState,
    );
    return headerState;
  }

  @override
  void set(PageState state, HeaderState headerState) {
    state.someState = headerState.someState;
  }
}
複製代碼
  • 上面例子將 store 中的 someState 分配給了 HeaderState,做爲 header 組件的 state。
  • 裏面的 set、get 方法會在更新 state 的時候自動調用。
  • 這裏的 HeaderConnector 會在 Page 建立的時候 dependencies 的 slots 中使用,做用是分配 store。

Action

類比於 redux 的 action,當發起 action 的時候,根據 action 名字匹配 reducer

例:

enum HomeAction {
  setSomeState,
}

class HomeActionCreator {
  static Action setSomeState(String someState) {
    return Action(HomeAction.setSomeState, payload: someState);
  }
}
複製代碼

官方推薦的作法是像上面同樣用兩個類管理 action

  • HomeAction 是一個枚舉類,做爲 action 的集合
  • HomeActionCreator 是一個 ActionCreator 類,在這裏面能夠約束 payload 類型,在 dispatch 的時候掉用 這個類中的函數並傳入 playload。

reducer

類比 redux 的 reducer,在這裏處理髮起的 action,並更新 state

例:

Reducer<PageState> buildHomeReducer() {
  return asReducer({
    HomeAction.setSomeState: setSomeStateHandle,
  });
}

PageState setSomeStateHandle(PageState state, Action action) {
    final newSomeState = action.payload;
    return state.clone()..someState = newSomeState;
}
複製代碼
  • buildHomeReducer 中對 action 與 reducer 進行了映射, 當 dispatch 名爲 setSomeState 的 action 時,調用下面的 setSomeStateHandle 方法
  • 上面的 setSomeStateHandle 使用了 PageState 的 clone 函數複製了一份新的數據,修改以後並 return
  • 須要注意的是,與 redux 同樣。fish_redux 的 state 也是 immutable 的,而且直接修改不生效,只能進行替換。

Middleware(中間件)

類比 redux 中的 Middleware,在 dispatch 時調用

例:

import 'package:fish_redux/fish_redux.dart';

Middleware<T> logMiddleware1<T>({
  String tag = 'redux',
  String Function(T) monitor,
}) {
  return ({Dispatch dispatch, Get<T> getState}) {
    return (Dispatch next) {
      print('1');
      return next;
    };
  };
}
複製代碼
  • 上面是一個最簡單的中間件,在 dispatch 的時候會打印 1, 當 return next 後會執行下一個中間件。
  • 中間件的執行順序是正序的。

Component

每個 Component 都是一個組件,它對視圖(view),修改數據(reducer), 非修改數據操做(effect)這三個邏輯進行了剝離。
Component = View + Effect(可選) + Reducer(可選) + Dependencies(可選)

例:

class HeaderComponent extends Component<HeaderState> {
  HeaderComponent()
    : super(
        view: buildView,
        effect: buildEffect(),
        reducer: buildReducer(),
        dependencies: Dependencies<HeaderState>(
            adapter: SomeAdapter(),
            slots: <String, Dependent<HeaderState>>{
              'avatar': AvatarConnector() + AvaterComponent()
            }),
      );
}
複製代碼
  • 這是一個 header 組件,裏面使用了 view、effect、reducer、dependencies 四個配置項目,下面挨個介紹

dependencies

dependencies 是一個表達組件之間依賴關係的結構。它包含兩個屬性 adapter 和 slots。

  • adapter: 組件依賴的具體適配器(用來構建高性能的 ListView)。
  • slots:組件依賴的插槽。
    • 上面的例子中,其中 slots 裏有個 ‘avatar’,這是說明 HeaderComponent 頁依賴 avatar 組件,其組件名叫 AvaterComponent。 AvaterComponent 的 state 在 AvatarConnector 裏被分配。
  • adapter 和 所依賴的組件 會在 view 中被 ViewService.buildComponent 調用使用
adapter

adapter 是對 listView 的封裝優化。適用於長列表的渲染。其有三種實現方式。

  • DynamicFlowAdapter 接受數組類型的數據驅動
  • StaticFlowAdapter 接受map類型的數據驅動
  • CustomAdapter 自定義 adapter
  1. DynamicFlowAdapter:
class SomeDynamicAdapter extends DynamicFlowAdapter<PageState> {
  SomeDynamicAdapter()
      : super(
          pool: <String, Component<Object>>{
            'task': Task(),
          },
          connector: Connector(),
          // reducer: buildReducer(),
          // filter: bindReducerFilter(),
          // effect: bindEffect(),
          // higherEffect: bindHeightEffect(),
          // onError: onError(),
        );
}

class Connector extends ConnOp<PageState, List<ItemBean>> {
  @override
  List<ItemBean> get(PageState state) {
    return state.taskList
      .map<ItemBean>((TaskState data) => ItemBean('task', data))
      .toList(growable: true);
  }
}
複製代碼
  • 這個文件有兩部分,一個是 adapter 主體類,一個是 adapter 的數據源 -- Connect
  • adapter 這裏只用到了 pool 與 connector 屬性。其還有 reducer、filter、effect 等其餘屬性,這裏不列舉。
  • pool 屬性用來講明 adapter 所依賴的子組件。這裏用到了一個名叫 'task' 的組件,會在下面的 Connector 中用來做爲列表的 itemView。
  • 使用 ItemBean 方法將 data 傳進 task 組件
  1. StaticFlowAdapter

staticFlowAdapter 接收 map 類型的數據

例:

class SomeStaticAdapter extends StaticFlowAdapter<PageState> {
  SomeStaticAdapter()
    : super(
      slots: [
        SomeComponent().asDependent(SomeComponenConnectt()),
        TaskAdapter().asDependent(TaskAdapterConnect()),
      ],
    );
}
複製代碼
  • StaticFlowAdapter 沒有 pool 屬性,可是有 slots,其餘的屬性與 DynamicFlowAdapter 相同。
  • slots 接受一個數組,裏面每個元素都是與 connect 鏈接好的 component 或 adapter。
  1. CustomFlowAdapter

CustomFlowAdapter 接受的屬性與 Component 同樣, 惟一的不一樣是 Adapter 的視圖部分返回的是一個 ListAdapter

例:

import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';

class CommentAdapter extends Adapter<PageState> {
    CommentAdapter()
        : super(
            adapter: buildCommentAdapter,
        );
}

ListAdapter buildCommentAdapter(PageState state, Dispatch dispatch, ViewService service) {
    final List builders = Collections.compact([
        _buildCommentHeader(state, dispatch, service),
    ]);

    if (state.commentList.length > 0) {
      builders.addAll(_buildCommentList(state, dispatch, service));
    } else {
      builders.addAll(_buildEmpty(state, dispatch, service));
    }

    return ListAdapter(
      (BuildContext buildContext, int index) => builders[index](buildContext, index),
      builders.length,
    );
}
複製代碼
  • 上面示例是一個 CustomFlowAdapter,其 adapter 是一個方法 buildCommentAdapter,這個方法返回了一個 ListAdapter。
  • buildCommentAdapter 里根據數據生成一個視圖列表。
  • 例如示例中若是 commentList.length > 0 就在 ListAdapter 中加入一個 評論列表,若是沒有評論就加入一個空視圖的 widget。

view

view 是一個輸出 Widget 的上下文無關的函數。其負責視圖層的構建,由 state 驅動。

例:

Widget buildView(HeaderState state, Dispatch dispatch, ViewService viewService) {
  final ListAdapter useAdapter = viewService.buildAdapter();

  return FlatButton(
      child: Container(
        child: ListView(
          children: <Widget>[
            viewService.buildComponent('avatar'),
            ListView.builder(
                  itemBuilder: useAdapter.itemBuilder,
                  itemCount: useAdapter.itemCount)
          ],
        ),
      ),
      onPressed: () {
        dispatch(HeaderActionCreator.profileOpen(state.profileIsOpen));
      },
  );
}

複製代碼
  • 這是一個 view,其內容是一個按鈕,點擊發起一個 action
  • 按鈕的 child 是他依賴的 avatar 組件,由 ViewService.buildComponent 調用。
  • onPressed 的時候調用 dispatch 事件,在對應的 reducer 中更新 state。

參數:

  1. state:傳進來的 state
  2. dispatch:發起 action
  3. viewService:調用 dependencies 所聲明依賴的 adapter 或 slots
    • viewService.buildAdapter: 調用依賴的 adapter
    • viewService.buildComponent(slotsName): 調用依賴的 slots
    • viewService.context: 當前 widget 的上下文(context), 很是經常使用
    • viewService.appBroadcast(Action action): 頁面間的廣播
    • viewService.pageBroadcast(Action action): 同一頁面中組件間的廣播

effect

Effect 是一個處理全部反作用的函數。我把它分爲兩種,一種是對生命週期的回調,一種是對非處理數據事件的回調。這裏面不作任何數據的處理,若是須要處理數據的話就發起一個 action 到 reducer 裏處理。

例:

Effect<PageState> buildEffect() {
  return combineEffects(<Object, Effect<PageState>>{
    Lifecycle.initState: _initStateEffect,
    'onClick': _onClickEffect,
  });
}

void _initStateEffect(Action action, Context context)async {
  final response = await fetch(
    'GET',
    'https://xxx.api.xxx',
    params: {
      'size': 10,
      'page': 1,
    }
  );
  context.dispatch(HomeActionCreator.onSetTaskDataAction(response));
}
複製代碼
  • 上面的 Effect 作了兩件事,一個是監聽 initState 生命週期;一個是處理 onClick action
  • 在 initState 的時候,發起了一個請求而且同步獲得 response,可是 state 是不能在這裏進行處理的,因此 context.dispatch 了一個 action 去 reducer 中處理。

Page

用來構建頁面,每一個頁面都有一個 Page 而且有一個 store。在這裏初始化 store,配置 Middleware,對 Redux 作 AOP 管理。它繼承於 Component

例:

// page, 繼承自 Component,PageState 是 store 裏定義的 state
class HomePage extends Page<PageState, Map<String, dynamic>> {
  HomePage()
      : super(
          view: buildView, 
          initState: initState,
          effect: buildEffect(),
          reducer: buildHomeReducer(),
          dependencies: Dependencies<PageState>(
            adapter: SomeAdapter(),
            slots: <String, Dependent<PageState>>{
              'header': HeaderConnector() + HeaderComponent(),
            }
          ),
          middleware: <Middleware<PageState>>[  // 中間件
            logMiddleware1(tag: 'HomePage'),
            logMiddleware2(tag: 'HomePage'),
          ],
        );
}
複製代碼
  • middleware 是一個數組,用來註冊中間件,示例中註冊了兩個中間件,他們會按序執行

其餘配置

除了上面那些 Component 還有其餘幾個比較簡單或不經常使用的配置,如:OnError 、HigherEffect 等,有時間再補充。

相關文章
相關標籤/搜索