@medux 基础概念速览

欢迎您开始 @medux 之旅,建议您依次阅读如下 4 篇文章,这将耗费您大约 30 分钟。javascript

第 2 篇:@medux 基础概念速览html

-- Github 地址 ---java

8 个新概念与名词

假设你了解过 Redux或者别的Flux框架,那么应当知道 Store、State、Reducer、Action、Dispatch 是什么意思。没错,在 @medux 中它们依然受用,只是 Action 的概念发生了一点微妙的变化,它具备更多 Event 事件的特性。react

Effect

咱们知道在 Redux 中,改变 State 必须经过 dispatch action 以触发 reducer。而 effect 是相对于 reducer 而言的,它也必须经过 dispatch action 来触发,不一样的是:webpack

  • 它是一个非纯函数,能够包含反作用,能够无返回,也能够是异步的
  • 它不能直接改变 State,必须再次 dispatch action 来触发 reducer

ActionHandler

咱们能够简单的认为:store.dispatch(action),能够触发 reducer 和 effect 执行,看起来 action 彷佛能够看成一种事件。reducer 和 effect 能够看成是该事件的监听者,因此 reducer 和 effect 统称为:ActionHandler。git

Module

咱们一般以高内聚、低偶合的原则进行模块划分,一个 Module 是相对独立的业务功能的集合,它一般包含一个 Model (用来处理业务逻辑) 和一组 View (用来展现数据与交互),须要注意的是:不要以 UI 视觉做为划分原则。github

Model

上面说过 Module 中包括一个model(维护数据)一组view(展示交互),而 model 主要包含两大职能:web

  • ModuleState 的定义
  • ActionHandler 的定义

数据流是从 Model 单向流入 View,因此 Model 是独立不依赖于 View 的。理论上即便没有 View,整个程序依然是能够经过数据来驱动。typescript

ModuleState、RootState

系统被划分为多个相对独立的 Module,不只体如今文件夹目录,更体如今 State 数据结构中。每一个 Module 负责维护和管理 State 下的一个子节点,咱们称之为 ModuleState,而整个 State 咱们习惯称之为RootStateapi

  • 每一个 ModuleState 都是 Store 的一级子节点,以 Module 名为 Key
  • 每一个 Module 只能修改自已的 ModuleState,可是能够读取其它 ModuleState
  • 每一个 Module 修改自已的 ModuleState,必须经过 dispatch action 来触发
  • 每一个 Module 能够监听其它 Module 发出的 action,来配合修改自已的 ModuleState

View、Component

View 本质上仍是一个 Component,它们有逻辑上的区别:

  • View 用来展现业务,Component 用来展现交互
  • View 必定属于某个 Module,Component 能够属于某个 Module 专用,也能够属于所有 Module
  • View 一般订阅了 Store,并从 Store 中之间得到数据,Component 则只能经过 props 来进行传递

典型工程结构

src
├── assets // 存放公共静态资源
├── entity // 存放业务实体类型定义
├── common // 存放公共代码
├── components // 存放UI公共组件
├── modules
│       ├── app //一个名为app的module
│       │     ├── assets //存放该module私有的静态资源
│       │     ├── components //存放该module私有的UI组件
│       │     ├── views
│       │     │     ├── TopNav
│       │     │     ├── BottomNav
│       │     │     └── ...
│       │     ├── model.ts //定义本模块model
│       │     └── index.ts //导出本模块
│       ├── photos //另外一个名为photos的module
│       │     └── ...
│       └── index.ts //模块配置与管理
└──index.ts 启动入口
复制代码

一个 model 的定义

// 定义本模块的ModuleState类型
export interface State extends BaseModuleState {
  listSearch: {username: string; page: number; pageSize: number};
  listItems: {uid: string; username: string; age: number}[];
  listSummary: {page: number; pageSize: number; total: number};
  loading: {
    searchLoading: LoadingState;
  };
}

// 定义本模块ModuleState的初始值
export const initState: State = {
  listSearch: {username: null, page: 1, pageSize: 20},
  listItems: null,
  listSummary: null,
  loading: {
    searchLoading: LoadingState.Stop,
  },
};

// 定义本模块全部的ActionHandler
class ModuleHandlers extends BaseModuleHandlers<State, RootState> {
  @reducer
  public putSearchList({listItems, listSummary}): State {
    return {...this.state, listItems, listSummary};
  }
  @effect('searchLoading')
  public async searchList(options: {username?: string; page?: number; pageSize?: number} = {}) {
    const listSearch = {...this.state.listSearch, ...options};
    const {listItems, listSummary} = await api.searchList(listSearch);
    this.dispatch(this.action.putSearchList({listItems, listSummary}));
  }
  // 能够监听其它Module发出的Action,而后改变自已的ModuleState
  @effect(null)
  protected async ['medux.RouteChange']() {
    if (this.rootState.route.location.pathname === '/list') {
      await this.dispatch(this.action.searchList());
    }
  }
}
复制代码

能静能动的模块加载机制

模块的加载策略一般集中在 modules/index.ts 中配置:

import * as app from 'modules/app';

// 定义模块的加载方案,同步或者异步都可
export const moduleGetter = {
  app: () => {
    // 使用import同步加载
    return app;
  },
  photos: () => {
    // 使用import()异步加载
    return import(/* webpackChunkName: "photos" */ 'modules/photos');
  },
};
复制代码

几个特殊的内置 Action

  • medux.RouteChange:路由发生变化时将触发此 action
  • medux.Error:捕获到 error 时将自动派发此 action
  • moduleName.Init:模块初次载入时会触发此 action
  • moduleName.RouteParams:模块路由参数发生变化时会触发此 action
  • moduleName.Loading:跟踪加载进度时会触发此 action

关于错误处理

执行发生错误时,框架会自动 dispatch 一个 type 为 medux.Error 的 errorAction,你能够监听此 action 来处理错误,例如:

@effect(null)
  protected async [ActionTypes.Error](error: CustomError) {
    if (error.code === '401') {
      this.dispatch(this.actions.putShowLoginPop(true));
    } else if (error.code === '404') {
      this.dispatch(this.actions.putShowNotFoundPop(true));
    } else {
      error.message && Toast.fail(error.message);
      //继续向上throw错误将致使运行中断
      throw error;
    }
  }
复制代码

路由 Store 化

medux 将路由视为另外一种 Store,它跟 Redux 的 Store 同样影响着 UI 的展现,在 Component 中你不用刻意区分引发 UI 变化的是 ReduxStore 仍是 RouteStore,它们都是同样的。好比:

评论区块的展现与关闭,你可使用 2 种方式来触发:

  • '/article/10' => '/article/10/showComments',路由变化能够引发评论区块的展现与关闭
  • {showComments: false} => {showComments: true},state 变化也能够达到一样的效果

究竟是使用路由来控制仍是 state 控制?咱们但愿 component 中不要作刻意的区分,这样后期修改方案时无需动到 component 自己。

你把路由当成另外一个 Store 就对了,只不过这个 RouteStore 能够任由用户在地址栏中直接修改,这和用户鼠标点击交互修改本质上是同样的。因此作好准备把 ReduxStore 中的一部分数据抽离出来放入 RouteStore 中,而后让用户经过 URL 任意修改吧...

路由的终极目的就是为了变动 UI,因此无论什么路由方案,总能解析出如下通用信息:

  • 当前路由会展现哪些 view
  • 以及展现这些 view 须要的参数

medux 将这些通用信息抽象成状态。至此,你能够忘掉路由了,一切都是 state,一切都遵循 UI=Render(State)。因而乎原来包含反作用的路由组件变成了普通组件:

//原来须要路由组件
<Switch>
  <Route exact path="/admin/home" component="{AdminHome}" />
  <Route exact path="/admin/role/:listView" component="{AdminRole}" />
  <Route path="/admin/member/:listView" component="{AdminMember}" />
</Switch>

//如今直接变成普通组件
<Switch>
  {routeViews.adminHome?.Main && <AdminHome />}
  {routeViews.adminRole?.List && <AdminRole />}
  {routeViews.adminMember?.List && <AdminMember />}
</Switch>
复制代码

具体如何提取通用信息,又如何将其转换成为状态呢?方案有不少种,我实现了一种:

你也能够实现更多 plan-b、plan-c...

CoreAPI

查看 CoreAPI 文档

Demo

medux-react-admin:基于@medux/react-web-router和最新的ANTD 4.x开发的通用后台管理系统,除了演示 medux 怎么使用,它还创造了很多独特的理念

继续阅读下一篇

@medux 路由篇

相关文章
相关标签/搜索