歡迎試用跨平臺前端框架@medux

歡迎您開始 @medux 之旅,建議您依次閱讀如下 4 篇文章,這將耗費您大約 30 分鐘。javascript

爲何你須要 @medux

-- Github 地址 ---html

一站式解決方案

一般一個前端工程包含以下職能:前端

  • UI 渲染框架
  • 狀態管理
  • 路由管理
  • 模塊化管理(包括模塊的定義、加載、維護)
  • 結構化管理(如何組織各種文件與資源)

其中 UI 框架與宿主平臺密切相關,比較獨立且複雜,一般有多種不一樣風格的解決方案可供選擇。而除此以外其它職能相對簡單,基本上均可以抽象爲通用跨平臺的 JS 運行時。vue

因此簡單來講,@medux想建立一個能夠對接不一樣 第三方UI框架的通用前端框架,它包含統一的狀態管理路由管理模塊化管理結構化管理等職能,能夠普遍運行於支持 JS 運行時的平臺上,這正是時下熱門的跨平臺跨端前端工程解決方案。java

加厚的狀態管理層

也許你還在猶豫是否是須要獨立的狀態管理層,由於把狀態管理寫在 UI 渲染層裏彷佛也挺順手。可是在@medux 看來,你不只須要把它們從 UI 中分離出來,並且還要儘量的剝離多一點,由於:react

  • 狀態層每每更抽象與穩定,UI 層更復雜與多變,將穩定的東西剝離出來能夠減小改動
  • 剝離狀態管理後的 UI 更純粹:UI=Render(State)
  • 不用考慮 UI 組件的生命週期以及各類鉤子,狀態管理也更簡單直觀
  • 不與特定 UI 相關聯,便於重用與多端跨平臺

基於 Redux 也支持 Mutable Data 的另外一種 Flux 框架

喜歡 vue 或 mobx 的朋友可能會問,medux 是要求可變數據仍是不可變數據?git

雖然 medux 是基於 redux 的,但本着實用至上的原則,並不要求嚴格遵循 redux 模型,它是另外一個 flux 框架。github

medux 框架內部會使用 ImmutableData 來自動生成並管理 state 及其 1 級節點,對於這個內置數據結構一般你也無需干預。而對於次級的 moduleState 你能夠將它定義爲 一個 MutableData,而後直接在 reducer 中修改 state 並返回它,儘管這有違 reducer 的本意,但這是對接 MutableData 最簡單靈活的方案。web

更鬆散的跨 Module 協做

在複雜的長業務流程中,跨模塊調用與協做是少不了的,不少框架都支持模塊化及跨模塊 dispatch action,可是它們每每只支持主動調用,例如:typescript

login(){
  ...
  if(response.success){
      dispatch({type: 'moduleB/someType'});
      dispatch({type: 'moduleC/someType'});
  }
}
複製代碼

medux 引入獨特的 actionHandler 機制,讓 action 能夠具備 Event 特性,因而你能夠在 moduleB、moduleC 中使用訂閱監聽模式:

{
  @reducer
  ['moduleA.login'](){
    //...doSomethings
  }
}
複製代碼

武裝到牙齒的類型推斷

medux 號稱一站式的前端框架,但它毫不是簡單的輪子拼湊,也不想作個鬆散的大雜燴,因此從一開始就使用 Typescript 編寫,而且將 UI 管理、狀態管理、模塊化管理使用各類類型推斷緊密結合起來。

type-check.png

去路由化

medux 刻意弱化了路由的概念,將路由視爲另外一種 Store,它跟 Redux 的 Store 同樣影響着 UI 的展現,在 component 中你不用刻意區分引發 UI 變化的是 ReduxStore 仍是 RouteStore,它們都是同樣的,嚴格遵循 UI=Render(State)

因此一些咱們常見的路由組件@medux 並不推薦使用,例如

<Switch>
  <Route exact path="/admin/home" component="{AdminHome}" />
  <Route exact path="/admin/role/:listView" component="{AdminRole}" />
  <Route path="/admin/member/:listView" component="{AdminMember}" />
</Switch>
複製代碼

它們的主要問題以下:

  • 將路由綁定到組件,render 再也不純粹,包含了外部環境的反作用
  • 將 path 硬編碼到組件中,不利於後期修改
  • path 做爲一個 string 類型,失去了類型推斷與檢查

那麼在@medux 中你能夠這樣改寫爲普通組件:

<Switch>
  {routeViews.adminHome?.Main && <AdminHome />}
  {routeViews.adminRole?.List && <AdminRole />}
  {routeViews.adminMember?.List && <AdminMember />}
</Switch>
複製代碼

優雅的支持 SSR 同構

網上不少號稱SSR同構的解決方案(例如 nextjs),要麼對 client 端有不少限制和要求,要麼 client 端和 server 端差異仍是很大。而 Medux 重狀態管理,輕UI的策略對 SSR 同構有着自然的支持。參見 Demo

更完全的模塊化

一個使用@medux 的典型工程結構:

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 啓動入口
複製代碼

其它網上經常使用的工程結構:

src
├── assets // 存放公共靜態資源
├── common // 存放公共代碼
├── components // 存放UI公共組件
├── routers // 配置路由加載器
├── layouts // 存放各類佈局版型
│       ├── LayoutA
│       ├── LayoutB
│       └── ...
├── pages // 存放各類頁面
│       ├── PageA
│       ├── user
│       │     ├── PageB
│       └── ...
├── views // 存放各類視圖
│       ├── ViewA
│       ├── user
│       │     ├── ViewB
│       └── ...
├── store // 存放模塊化的狀態管理
│       ├── modules
│       │     ├── modelA
│       │     ├── modelB
│       │     └── ...
│       └── index.ts //store配置與管理
└──index.ts 啓動入口
複製代碼

對好比下:

  • medux 使用 module 爲一級分類,module 下面再分 model、components、view、assets。其它常見框架一般只對 model 部分使用模塊化,而 components、view 和 assets 並未很好的模塊化
  • medux 分模塊依據的是 高內聚低耦合的業務內在邏輯。其它常見框架一般分模塊的依據是UI 視覺
  • medux 將一個模塊總體打包成一個 bundle,模塊能夠插拔與按需加載。其它常見框架一般對一個 view 打包成一個 bundle,從實際業務場景出發,咱們一般須要插拔的是整個業務功能模塊,而不只僅是一個 view
  • medux 對於 view 和 component 有清晰的定位與界限:component 爲 UI 交互控件,只能經過 props 傳值不能夠直接使用 ReduxStore,而 view 是業務視圖,它能夠直接使用 ReduxStore。其它常見框架對於 component 與 view 並沒有清晰的定位,一般是依據視覺上主觀感覺
  • medux 只強制區分 view 和 component,由於若是不能給出明確的界限就不要讓用戶迷茫。其它常見框除此以外還定義了 layouts、routers、pages。那麼問題來了:
    • 在 single 單頁應用中,page 概念已經變得很模糊,何爲 page?
    • UI 組件都支持嵌套或者 slot 插槽,layout 概念也已經變得很模糊
    • 路由變化能夠引發 UI 的加載與卸載,State 變化一樣能夠,爲何要區分路由組件和普通組件

能靜能動的模塊加載機制

模塊能夠同步加載,也能夠異步按需加載。可是咱們在開發過程當中不能將模塊的加載邏輯混入業務邏輯中,這樣會讓問題更復雜。medux 中的模塊加載被視爲一種策略能夠隨時更改,除了配置文件,無需更多代碼變動。

@medux 概述

本框架前身是我早些年寫的另外一個框架 react-coat,它由於捆綁了 React UI 框架,變得再也不純粹。

如今 @medux 被封裝成了一系列 npm 包,它們從抽象到具體,你能夠選配某些包並進行二次開發,也能夠直接使用開箱即用的平臺 UI 集成包

包含如下 Packages

  • @medux/core:核心基礎包
  • @medux/web:讓 @medux/core 具備 web 特性,主要體如今 History 管理上
  • @medux/route-plan-a:實現一套基於 @medux/core 的跨平臺路由方案,它將 web 的路由風格帶入其它平臺
  • @medux/react:@medux/core 結合 React 的封裝
  • @medux/react-web-router:整合封裝了@medux/core、@medux/web、@medux/route-plan-a、@medux/react, 是 web 環境下開發 react 的開箱即用框架

如下是正在開發,還沒有完成的 Packages:

  • @medux/vue-web-router:@medux/core 結合 VUE,思路很簡單,在 Reducer 中直接修改 ModuleState 而後返回它
  • @medux/react-native-router:@medux/core 結合 ReactNative

兼容性

支持 IE8 及以上現代瀏覽器。IE11 及如下瀏覽器請自行加入polyfill,並使用 src 目錄的 TS 源碼從新編譯。

參見具體細節

model 代碼風格

如下是某個使用 @medux 的 model,能夠先大概感覺一下它的風格:

// 僅需一個類,搞定 action、dispatch、reducer、effect、loading
export class ModelHandlers extends BaseModelHandlers<State, RootState> {
  @reducer
  protected putCurUser(curUser: CurUser): State {
    return {...this.state, curUser};
  }
  @reducer
  public putShowLoginPop(showLoginPop: boolean): State {
    return {...this.state, showLoginPop};
  }
  @effect('login') // 將loading狀態注入key爲login的state中
  public async login(payload: {username: string; password: string}) {
    const loginResult = await sessionService.api.login(payload);
    if (!loginResult.error) {
      this.dispatch(this.actions.putCurUser({curUser: loginResult.data}));
      Toast.success('歡迎您回來!');
    } else {
      Toast.fail(loginResult.error.message);
    }
  }
  // model內錯誤會觸發medux.ERROR的action,監聽併發送給後臺
  @effect(null) // 設置爲null表示不須要跟蹤loading
  protected async ['medux.ERROR'](error: CustomError) {
    if (error.code === '401') {
      this.dispatch(this.actions.putShowLoginPop(true));
    } else if (error.code === '301' || error.code === '302') {
      //路由跳轉
      historyActions.replace(error.detail);
    } else {
      Toast.fail(error.message);
      await settingsService.api.reportError(error);
    }
  }
  // 監聽自已的INIT Action,作一些異步數據請求
  @effect()
  protected async ['this.INIT']() {
    const [projectConfig, curUser] = await Promise.all([settingsService.api.getSettings(), sessionService.api.getCurUser()]);
    this.dispatch(
      this.actions.updateState({
        projectConfig,
        curUser,
      })
    );
  }
}
複製代碼

在 view 中 dispatchAction

擁有豐富的 typescript 類型推斷與反射是 medux 的一大特色:

4.png

CoreAPI

查看 CoreAPI 文檔

Demo

medux-react-admin:基於@medux/react-web-router和最新的ANTD 4.x開發的通用後臺管理系統,除了演示 medux 怎麼使用,它還創造了很多獨特的理念

繼續閱讀下一篇

medux 基礎概念速覽

相關文章
相關標籤/搜索