前端技術 | dva,美貌與智慧並存

通過前面四篇的鋪墊,終於輪到咱們的主角dva了,就是下面這個美女:css

先擦一擦哈喇子,咱們來介紹一下,dva出自於暴雪出品的一款遊戲《守望先鋒》,援引官方的角色介紹:前端

D.Va擁有一部強大的機甲,它具備兩臺全自動的近距離聚變機炮、可使機甲飛躍敵人或障礙物的推動器、 還有能夠抵禦來自正面的遠程攻擊的防護矩陣。react

而後呢,螞蟻金服的一位架構師sorrycc很迷這位美女,正巧剛開發了一款前端框架沒有名字,做爲一個向女神獻禮的項目,dva框架就此誕生。git

實際上,dva只是基於現有開源框架的一層輕量封裝,並無引入任何新概念:github

  • React:管理View
  • react-router:管理路由
  • Redux:管理Model
  • redux-saga:管理異步調用(反作用)

再來看一下框架圖,是否是都是熟悉的配方,熟悉的味道?web

固然,也不是徹底沒有新東西,其中有一個Subscription好像以前沒有見過,這是一種數據源訂閱機制,數據源能夠是鍵盤輸入事件、路由變化、服務器的 websocket 鏈接等等。你能夠在數據發生變化時收到通知,並派發必要的action。redux

實際上,dva是一個整合者,它的目標是解決「Code is everywhere」問題。當咱們同時使用上面這些框架時,通常會呈現下面這種類型的文件結構:segmentfault

+ src
  + sagas
    - user.js
  + reducers
    - user.js
  + actions
    - user.js
複製代碼

而後,當咱們須要實現一個功能時,就須要在這幾個文件之間來回切換。。。api

另外一方面,dva還試圖隱藏一些常常重複書寫的routine代碼,讓開發者可以更加專一於業務邏輯。好比咱們寫一個應用的入口文件,須要作下面這麼多事情:bash

  • 配置middleware
  • 建立store
  • 添加<Provider>綁定
  • 建立watcher saga和root saga
  • 啓動saga
  • 。。。

實際上,可能95%以上的項目中這些代碼都是如出一轍的,咱們不須要每次都花費時間來從新寫一遍這些代碼。

下面開始正式介紹dva 1.0相關的內容,dva 2.0作了一些優化升級,後面專門有一節介紹具體變化。

8個概念

其實基本都是前面幾篇文章裏介紹過的概念:

  • state:也就是全局惟一的Store
  • action:即Redux中的action
  • model:這是dva抽象出來的一個概念,爲了把下面這些東西放到一個統一的文件裏
    • reducer:即Redux中的reducer
    • effect:即redux-saga中的worker saga
    • subscription:前面介紹過,用於訂閱數據源
  • router:即react-router中的<Router>
    • route:即react-router中的<Route/>

最終寫出來的model.js會相似下面這個樣子,能夠發現全部相關代碼都放到一塊兒了,不須要在多個文件之間來回切換了(這裏的namespace就是之前Redux中的reducer的名字):

export default {
  namespace: 'transactions',
  state: {
    txs: []
  },
  subscriptions: {
    setup({ dispatch, history }) {
      history.listen(location => {
        if (location.pathname === '/transactions/list') {
          dispatch({type: 'fetch'});
        }
      });
    },
  },
  effects: {
    *fetch({ payload }, {call, select, put}) {
      const { result } = yield call(apis.fetchTxs)
      yield put({type:'addTx', payload: result})
    },
  },
  reducers: {
    addTx(state, { payload }) {
      return { ...state, txs: payload };
    },
  },
}
複製代碼

7個API

dva只有7個API,因此上手基本上沒有什麼難度:

  • app = dva(opts):建立dva對象
  • app.use(hooks):使用插件(後面介紹)
  • app.model(model):註冊model
  • app.unmodel(namespace):取消model註冊
  • app.replaceModel(model):替換model
  • app.router(({ history, app }) => RouterConfig):配置路由
  • app.start(selector?):啓動應用(參數是根組件id)

下面這個連接展現了5步建立單頁應用的例子:github.com/sorrycc/blo…

4大模塊

這個主要是從代碼結構上來劃分的,通常分爲下面4類模塊:

  • service:執行異步任務的API
  • model:包含effect, reducer, subscription
  • component:無狀態組件
    • 發送action觸發狀態更新
    • action = 'namespace/effect'或者'namespace/reducer'
  • route:各類container(這個名字取得不太好)
    • 經過connect()鏈接到model

另外,因爲dva 1.0使用的是react-router v3,因此最外層還有一個router.js用於配置靜態路由。因此通常的目錄結構以下所示:

+ src/
  + services/
    - users.js
  + models/
    - users.js
  + components/
    + users/
      - users.js
      - users.css
  + routes/
    - users.js
  - router.js
複製代碼

dva 2.0中採用了react-router v4,就不須要router.js了。另外,如今官方推薦搭配使用umi(烏米,sorrycc最新的開源項目),能夠自動幫你註冊model、根據目錄結構生成路由配置,目錄結構會變成下面這個樣子:

+ src/
  + models/
    - global.js
  + pages/
    + users/
      + index.js
      + services
        - users.js
      + models/
        - users.js
  + components/
    + users/
      - users.js
      - users.css
複製代碼

能夠發現,把route以及相關聯的model都放到pages目錄中了,能夠達到減小耦合,一刪全刪的功能。

插件(Plugin)

前面提到過一個API能夠註冊「插件」,所謂插件就是一些生命週期「鉤子(hooks)」,能夠在事件發生時作一些額外處理。包括下面這些類型的鉤子:

  • onError
  • onStateChange
  • onAction
  • onHmr:熱替換(Hot Module Replacement)
  • onReducer:reducer被調用
  • onEffect:effect被調用
  • extraReducers:額外的reducer被調用(好比搭配合redux-form)
  • extraEnhancers:額外的store enhancer被調用(好比搭配redux-persist)

一個比較典型的例子是頁面加載數據時轉圈圈,加載完畢後隱藏,這是一個不少地方都須要用到的場景。官方提供了一個dva-loading插件,能夠自動在異步請求數據時把loading設置爲true,得到數據後把loading設置爲false。這樣你就能夠在組件中綁定loading實現轉圈圈的自動控制了:

// index.js
import createLoading from 'dva-loading';
app.use(createLoading());

// components
function mapStateToProps(state) {
  const { list, total, page } = state.users;
  return {
    loading: state.loading.models.users,
    list,
    total,
    page,
  };
}
複製代碼

和redux+saga的對應關係

雖然dva只是一層輕量級封裝,可是作了一些特殊的命名約定,剛開始寫代碼的時候會有點迷糊,搞不清楚跟以前直接使用redux+saga的時候的對應關係,這裏也幫你們梳理一下。

直接使用redux+saga的流程以下所示:

  • component發送請求action
  • saga調用service,而後put一個結果action
  • reducer從action中獲取type和payload,修改state(可使用combinedReducer,會依次遍歷)
  • component在mapStateToProps()中經過state.<reducer名字>獲取更新

使用dva時的流程以下所示:(觸發effect爲例)

  • component發送請求action,type約定爲namespace/effect
  • effect調用service,而後put一個結果action,type約定爲namespace/reducer(同一個model中能夠省略namespace)
  • reducer從action中獲取type和payload,修改state(每一個reducer只處理一種類型的action,type約定爲reducer的名字
  • component在mapStateToProps()中經過state.<namespace>獲取更新

dva2.0的變更

目前dva已經進化到2.0版本,除了採用了react-router v4之外,還有一些細節上的變更:

  • dispatch一個effect 類型的action時返回一個Promise,方便視圖層回調

  • 新增 dynamic 接口,配合 react-router@4 處理組件的按需加載

  • take 自動補全 namespace 前綴

  • effect 先後會額外觸發 /@@start/@@end 的 action,可利用此約定實現 put 的同步執行

  • 同名 reducer 和 effect 不會 fallthrough(即二者都執行),而是僅執行 effect

具體細節能夠參見:github.com/sorrycc/blo…

今天就說到這裏,老規矩,上一張思惟導圖結束本篇文章:

參考:

dvajs.com/guide

github.com/dvajs/dva/i…

www.github.com/sorrycc/blo…

github.com/sorrycc/blo…

github.com/sorrycc/blo…

juejin.im/post/5b93c0…

github.com/sorrycc/blo…

segmentfault.com/a/119000001…

相關文章
相關標籤/搜索