欢迎您开始 @medux 之旅,建议您依次阅读如下 4 篇文章,这将耗费您大约 30 分钟。javascript
第 2 篇:@medux 基础概念速览html
-- Github 地址 ---java
假设你了解过 Redux
或者别的Flux
框架,那么应当知道 Store、State、Reducer、Action、Dispatch 是什么意思。没错,在 @medux 中它们依然受用,只是 Action 的概念发生了一点微妙的变化,它具备更多 Event 事件的特性。react
咱们知道在 Redux 中,改变 State 必须经过 dispatch action 以触发 reducer。而 effect 是相对于 reducer 而言的,它也必须经过 dispatch action 来触发,不一样的是:webpack
咱们能够简单的认为:store.dispatch(action),能够触发 reducer 和 effect 执行,看起来 action 彷佛能够看成一种事件。reducer 和 effect 能够看成是该事件的监听者,因此 reducer 和 effect 统称为:ActionHandler。git
咱们一般以高内聚、低偶合的原则进行模块划分,一个 Module 是相对独立的业务功能的集合,它一般包含一个 Model (用来处理业务逻辑) 和一组 View (用来展现数据与交互),须要注意的是:不要以 UI 视觉做为划分原则。github
上面说过 Module 中包括一个model(维护数据)
和一组view(展示交互)
,而 model 主要包含两大职能:web
数据流是从 Model 单向流入 View,因此 Model 是独立不依赖于 View 的。理论上即便没有 View,整个程序依然是能够经过数据来驱动。typescript
系统被划分为多个相对独立的 Module,不只体如今文件夹目录,更体如今 State 数据结构中。每一个 Module 负责维护和管理 State 下的一个子节点,咱们称之为 ModuleState,而整个 State 咱们习惯称之为RootStateapi
View 本质上仍是一个 Component,它们有逻辑上的区别:
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 启动入口
复制代码
// 定义本模块的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');
},
};
复制代码
执行发生错误时,框架会自动 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;
}
}
复制代码
medux 将路由视为另外一种 Store,它跟 Redux 的 Store 同样影响着 UI 的展现,在 Component 中你不用刻意区分引发 UI 变化的是 ReduxStore 仍是 RouteStore,它们都是同样的。好比:
评论区块的展现与关闭,你可使用 2 种方式来触发:
究竟是使用路由来控制仍是 state 控制?咱们但愿 component 中不要作刻意的区分,这样后期修改方案时无需动到 component 自己。
你把路由当成另外一个 Store 就对了,只不过这个 RouteStore 能够任由用户在地址栏中直接修改,这和用户鼠标点击交互修改本质上是同样的。因此作好准备把 ReduxStore 中的一部分数据抽离出来放入 RouteStore 中,而后让用户经过 URL 任意修改吧...
路由的终极目的就是为了变动 UI,因此无论什么路由方案,总能解析出如下通用信息:
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...
medux-react-admin:基于@medux/react-web-router
和最新的ANTD 4.x
开发的通用后台管理系统,除了演示 medux 怎么使用,它还创造了很多独特的理念