@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 路由篇

相關文章
相關標籤/搜索