一、 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

    二、关键 API

    1. Route

    fish-redux 有一套自己的路由,有三种 路由方式
  • AppRoutes:多个 page 共享一个 store
  • PageRoutes: 页面级别的路由,每个页面都有一个 store
  • HybridRoutes:可以结合上面两种 route 方式

    AppRoutes

    AppRoutes 包含了 State, Reducer, pages && connector,可以将 store 中的数据 分发到各个页面。其组装方式跟 Page 差不多哦。

例:

  1. final AbstractRoutes appRoutes = AppRoutes<AppState>(
  2. preloadedState: AppState.initState(),
  3. pages: {
  4. 'homePage': HomePageConnector() + HomePage(),
  5. },
  6. reducer: buildReducer()
  7. );
  8. return MaterialApp(
  9. home: appRoutes.buildPage('homePage', null),
  10. onGenerateRoute: (RouteSettings settings) {
  11. return MaterialPageRoute<Object>(builder: (BuildContext context) {
  12. return appRoutes.buildPage(settings.name, settings.arguments);
  13. });
  14. },
  15. );
  16. /// 想要使用 AppRoutes 发起一个 action,需要将它保存起来,使用它的 store
  17. /// 像下面这样
  18. appRoutes.store.dispatch(AppActionCreator.someEvent());
  • 使用 AppRoutes 创建了一个路由 -> appRoutes
  • 使用 preloadedState 属性初始化 state
  • 里面定义了 homePage 页面,HomePageConnector 中将某些 state 分配给 homePage。

    PageRoutes

    PageRoutes 使用起来就比较简单,因为其不需要定义与分配 state,每个页面的 store 在其自身的 Page 里定义。
    例:
    1. final AbstractRoutes pageRoutes = PageRoutes(
    2. pages: <String, Page<Object, dynamic>>{
    3. 'homePage': HomePage(),
    4. },
    5. );
    6. return MaterialApp(
    7. home: pageRoutes.buildPage('homePage', null),
    8. onGenerateRoute: (RouteSettings settings) {
    9. return MaterialPageRoute<Object>(builder: (BuildContext context) {
    10. return pageRoutes.buildPage(settings.name, settings.arguments);
    11. });
    12. },
    13. );

    HybridRoutes

    HybridRoutes 接受一个 route 数组,数组里既可以接受 AppRoutes 也可以接受 PageRoutes

例:

  1. final AbstractRoutes hybridRoutes = HybridRoutes(routes: <AbstractRoutes>[
  2. appRoutes,
  3. pageRoutes
  4. ]);

2. 状态管理

Store

1、 定义 store 数据
使用 class 来声明全局 state,在 声明 page 的时候注入
例:

  1. class PageState implements Cloneable <PageState> {
  2. String someState;
  3. @override
  4. PageState clone() {
  5. return PageState()
  6. ..someState = someState
  7. }
  8. }
  • 上面有个 clone 方法,用来获取 state,在 reducer 中会用到用来 merge state。
  • 需要注意的是,state 必须 继承自 Cloneable,否则注册不上 reducer
  • 如果没有特殊场景需要,在 clone 中要把所有的 state 写全,不然当在 reducer 中使用 clone 方法进行 merge 数据的时候,会丢掉里面缺少的数据。
  1. 使用 Connect 给组件分配 state

类比于 redux 的 connect

  1. class HeaderConnector extends ConnOp<PageState, HeaderState> {
  2. @override
  3. HeaderState get(PageState state) {
  4. final HeaderState headerState = HeaderState(
  5. someState: state.someState,
  6. );
  7. return headerState;
  8. }
  9. @override
  10. void set(PageState state, HeaderState headerState) {
  11. state.someState = headerState.someState;
  12. }
  13. }
  • 上面例子将 store 中的 someState 分配给了 HeaderState,作为 header 组件的 state。
  • 里面的 set、get 方法会在更新 state 的时候自动调用。
  • 这里的 HeaderConnector 会在 Page 创建的时候 dependencies 的 slots 中使用,作用是分配 store。

    Action

    类比于 redux 的 action,当发起 action 的时候,根据 action 名字匹配 reducer
    例:

    1. enum HomeAction {
    2. setSomeState,
    3. }
    4. class HomeActionCreator {
    5. static Action setSomeState(String someState) {
    6. return Action(HomeAction.setSomeState, payload: someState);
    7. }
    8. }

    官方推荐的做法是像上面一样用两个类管理 action

  • HomeAction 是一个枚举类,作为 action 的集合

  • HomeActionCreator 是一个 ActionCreator 类,在这里面可以约束 payload 类型,在 dispatch 的时候掉用 这个类中的函数并传入 playload。

    reducer

    类比 redux 的 reducer,在这里处理发起的 action,并更新 state
    例:

    1. Reducer<PageState> buildHomeReducer() {
    2. return asReducer({
    3. HomeAction.setSomeState: setSomeStateHandle,
    4. });
    5. }
    6. PageState setSomeStateHandle(PageState state, Action action) {
    7. final newSomeState = action.payload;
    8. return state.clone()..someState = newSomeState;
    9. }
  • buildHomeReducer 中对 action 与 reducer 进行了映射, 当 dispatch 名为 setSomeState 的 action 时,调用下面的 setSomeStateHandle 方法

  • 上面的 setSomeStateHandle 使用了 PageState 的 clone 函数复制了一份新的数据,修改之后并 return
  • 需要注意的是,与 redux 一样。fish_redux 的 state 也是 immutable 的,并且直接修改不生效,只能进行替换。

    Middleware

    类比 redux 中的 Middleware,在 dispatch 时调用
    例:

    1. import 'package:fish_redux/fish_redux.dart';
    2. Middleware<T> logMiddleware1<T>({
    3. String tag = 'redux',
    4. String Function(T) monitor,
    5. }) {
    6. return ({Dispatch dispatch, Get<T> getState}) {
    7. return (Dispatch next) {
    8. print('1');
    9. return next;
    10. };
    11. };
    12. }
  • 上面是一个最简单的中间件,在 dispatch 的时候会打印 1, 当 return next 后会执行下一个中间件。

  • 中间件的执行顺序是正序的。

Connector

connector理解为子数据与父数据绑定,在注册component以及adapter使用

3. Component

每一个 Component 都是一个组件,它对视图(view),修改数据(reducer), 非修改数据操作(effect)这三个逻辑进行了剥离。
Component = View + Effect(可选) + Reducer(可选) + Dependencies(可选)
例:

  1. class HeaderComponent extends Component<HeaderState> {
  2. HeaderComponent()
  3. : super(
  4. view: buildView,
  5. effect: buildEffect(),
  6. reducer: buildReducer(),
  7. dependencies: Dependencies<HeaderState>(
  8. adapter: SomeAdapter(),
  9. slots: <String, Dependent<HeaderState>>{
  10. 'avatar': AvatarConnector() + AvaterComponent()
  11. }),
  12. );
  13. }
  • 这是一个 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 :::success 官方推荐使用SourceFlowAdapter代替 DynamicFlowAdapter :::
    1. class SomeDynamicAdapter extends DynamicFlowAdapter<PageState> {
    2. SomeDynamicAdapter()
    3. : super(
    4. pool: <String, Component<Object>>{
    5. 'task': Task(),
    6. },
    7. connector: Connector(),
    8. // reducer: buildReducer(),
    9. // filter: bindReducerFilter(),
    10. // effect: bindEffect(),
    11. // higherEffect: bindHeightEffect(),
    12. // onError: onError(),
    13. );
    14. }
    15. class Connector extends ConnOp<PageState, List<ItemBean>> {
    16. @override
    17. List<ItemBean> get(PageState state) {
    18. return state.taskList
    19. .map<ItemBean>((TaskState data) => ItemBean('task', data))
    20. .toList(growable: true);
    21. }
    22. }
  • 这个文件有两部分,一个是 adapter 主体类,一个是 adapter 的数据源 — Connect
  • adapter 这里只用到了 pool 与 connector 属性。其还有 reducer、filter、effect 等其他属性,这里不列举。
  • pool 属性用来说明 adapter 所依赖的子组件。这里用到了一个名叫 ‘task’ 的组件,会在下面的 Connector 中用来作为列表的 itemView。
  • 使用 ItemBean 方法将 data 传进 task 组件
  1. StaticFlowAdapter

staticFlowAdapter 接收 map 类型的数据
例:

  1. class SomeStaticAdapter extends StaticFlowAdapter<PageState> {
  2. SomeStaticAdapter()
  3. : super(
  4. slots: [
  5. SomeComponent().asDependent(SomeComponenConnectt()),
  6. TaskAdapter().asDependent(TaskAdapterConnect()),
  7. ],
  8. );
  9. }
  • StaticFlowAdapter 没有 pool 属性,但是有 slots,其他的属性与 DynamicFlowAdapter 相同。
  • slots 接受一个数组,里面每一个元素都是与 connect 连接好的 component 或 adapter。
  1. CustomFlowAdapter

CustomFlowAdapter 接受的属性与 Component 一样, 唯一的不同是 Adapter 的视图部分返回的是一个 ListAdapter
例:

  1. import 'package:fish_redux/fish_redux.dart';
  2. import 'package:flutter/material.dart';
  3. class CommentAdapter extends Adapter<PageState> {
  4. CommentAdapter()
  5. : super(
  6. adapter: buildCommentAdapter,
  7. );
  8. }
  9. ListAdapter buildCommentAdapter(PageState state, Dispatch dispatch, ViewService service) {
  10. final List builders = Collections.compact([
  11. _buildCommentHeader(state, dispatch, service),
  12. ]);
  13. if (state.commentList.length > 0) {
  14. builders.addAll(_buildCommentList(state, dispatch, service));
  15. } else {
  16. builders.addAll(_buildEmpty(state, dispatch, service));
  17. }
  18. return ListAdapter(
  19. (BuildContext buildContext, int index) => builders[index](buildContext, index),
  20. builders.length,
  21. );
  22. }
  • 上面示例是一个 CustomFlowAdapter,其 adapter 是一个方法 buildCommentAdapter,这个方法返回了一个 ListAdapter。
  • buildCommentAdapter 里根据数据生成一个视图列表。
  • 例如示例中如果 commentList.length > 0 就在 ListAdapter 中加入一个 评论列表,如果没有评论就加入一个空视图的 widget。

    view

    view 是一个输出 Widget 的上下文无关的函数。其负责视图层的构建,由 state 驱动。
    例:

    1. Widget buildView(HeaderState state, Dispatch dispatch, ViewService viewService) {
    2. final ListAdapter useAdapter = viewService.buildAdapter();
    3. return FlatButton(
    4. child: Container(
    5. child: ListView(
    6. children: <Widget>[
    7. viewService.buildComponent('avatar'),
    8. ListView.builder(
    9. itemBuilder: useAdapter.itemBuilder,
    10. itemCount: useAdapter.itemCount)
    11. ],
    12. ),
    13. ),
    14. onPressed: () {
    15. dispatch(HeaderActionCreator.profileOpen(state.profileIsOpen));
    16. },
    17. );
    18. }
  • 这是一个 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 里处理。
      例:
      1. Effect<PageState> buildEffect() {
      2. return combineEffects(<Object, Effect<PageState>>{
      3. Lifecycle.initState: _initStateEffect,
      4. 'onClick': _onClickEffect,
      5. });
      6. }
      7. void _initStateEffect(Action action, Context context)async {
      8. final response = await fetch(
      9. 'GET',
      10. 'https://xxx.api.xxx',
      11. params: {
      12. 'size': 10,
      13. 'page': 1,
      14. }
      15. );
      16. context.dispatch(HomeActionCreator.onSetTaskDataAction(response));
      17. }
  • 上面的 Effect 做了两件事,一个是监听 initState 生命周期;一个是处理 onClick action
  • 在 initState 的时候,发起了一个请求并且同步得到 response,但是 state 是不能在这里进行处理的,所以 context.dispatch 了一个 action 去 reducer 中处理。

    Page

    用来构建页面,每个页面都有一个 Page 并且有一个 store。在这里初始化 store,配置 Middleware,对 Redux 做 AOP 管理。它继承于 Component
    例:

    1. // page, 继承自 Component,PageState 是 store 里定义的 state
    2. class HomePage extends Page<PageState, Map<String, dynamic>> {
    3. HomePage()
    4. : super(
    5. view: buildView,
    6. initState: initState,
    7. effect: buildEffect(),
    8. reducer: buildHomeReducer(),
    9. dependencies: Dependencies<PageState>(
    10. adapter: SomeAdapter(),
    11. slots: <String, Dependent<PageState>>{
    12. 'header': HeaderConnector() + HeaderComponent(),
    13. }
    14. ),
    15. middleware: <Middleware<PageState>>[ // 中间件
    16. logMiddleware1(tag: 'HomePage'),
    17. logMiddleware2(tag: 'HomePage'),
    18. ],
    19. );
    20. }
  • middleware 是一个数组,用来注册中间件,示例中注册了两个中间件,他们会按序执行

作者:jserTang 链接:https://juejin.im/post/5cefb0f7f265da1bc07e1e59 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

学习资料