歡迎您開始 @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 怎麼使用,它還創造了很多獨特的理念