博客 / 詳情

返回

Dva.js 快速上手指南

先説些廢話

最近在開發React技術棧的項目產品,對於數據狀態的管理使用了Dva.js,作為一個資深的ow玩家,我看到這個名字第一反應就是————這不是ow裏的一個女英雄嗎?仔細閲讀了官方文檔之後,發現開發者還真是因為這個角色獲得靈感,來命名這個數據狀態管理插件,果然開發大佬都是工作和休閒兩不誤~

學過React的同學都知道它的技術棧非常多且雜,所以每當你使用React的時候都需要引入很多的模塊,那麼Dva就是把這些用到的模塊集成在一起,比如一些需要引入的依賴react-saga/react-loger、必寫的ReactDOM.renderprovider、connect包裹等都省去不寫,形成一定的架構規範,大大提高我們的開發效率

今天,就來寫一份文檔,幫助後續使用Dva的開發者更好得在實際項目中(PS:需要是以UMI為基礎框架,純Dva來構建項目可以直接看文章結尾的參考文檔列表)上手使用

什麼是Dva

Dva首先是一個基於reduxredux-saga的數據流方案,然後為了簡化開發體驗,Dva還額外內置了react-routerfetch,所以也可以理解為一個輕量級的應用框架。

在我目前的項目中,更多是使用數據狀態管理的功能,他在我司的fish框架中做了內嵌,在主流的React開發框架UMI中也做了內嵌適配,使用起來非常方便快速。

Dva設計的目的就是簡化元素,降低難度,讓你不用管他怎麼實現的,我們按照默認的這個規則去寫就可以

數據流向

數據的改變發生通常是通過用户交互行為或者瀏覽器行為(如路由跳轉等)觸發的,當此類行為會改變數據的時候可以通過dispatch發起一個action
如果是同步行為會直接通過reducers改變states,如果是異步行為(副作用)會先觸發effects然後流向reducers最終改變states

Dva數據流向

分層開發

無論是Vue還是React開發,實際的大型應用一定有嚴格的分層開發規範,確保後續開發的可維護性,主要的分層結構有以下幾點:

  • Page 負責與用户直接打交道:渲染頁面,接受用户的操作輸入,側重於展示型交互性邏輯,這裏需要了解無狀態組件
  • Model 負責處理業務邏輯,為 Page 做數據、狀態的讀寫、變換、暫存等,Dvamodel就是做了這一層的操作
  • Service 負責與 HTTP 接口對接,進行純粹的數據讀寫

基礎概念

  1. namespace

    • model的命名空間,同時也是他在全局state上的屬性
    • 只能用字符串,不支持通過.的方式創建多層命名空間,相當於這個modelkey
    • 在組件裏面,通過connect這個key將想要引入的model加入

      import { connect } from 'dva';
      export default connect(({ namespaceValue }) => ({ ...namespaceValue }))(DvaCompoent);
  2. state

    • 表示model的狀態數據
    • 操作的時候每次都要當作不可變數據immutable data來對待,保證每次都是全新對象,沒有引用關係
  3. reducer

    • 必須是純函數,有固定輸入輸出,主要目的是修改自身state
    • 接受兩個參數:之前已經累積運算的結果和當前要被累積的值,返回的是一個新的累積結果,該函數把一個集合歸併成一個單值
    • 需要注意的是同樣的輸入必然得到同樣的輸出,它們不應該產生任何副作用effect。並且,每一次的計算都應該使用immutable data
  4. effect

    • 主要用於異步請求,接口調用之類的
    • effect被稱為副作用,在我們的應用中,最常見的就是異步操作
    • 它來自於函數編程的概念,之所以叫副作用是因為它使得我們的函數變得不純,同樣的輸入不一定獲得同樣的輸出
  5. subscription

    • subscription語義是訂閲,用於訂閲一個數據源,然後根據條件dispatch需要的action
    • 數據源可以是當前的時間、服務器的websocket連接、keyboard輸入、geolocation變化、history路由變化等等
    • 內部定義的函數都會被被執行,執行之後作為監聽來處理事務
  6. dispatch

    • dispatch是一個用於觸發action的函數,action是改變state的唯一途徑,但是它只描述了一個行為,而dipatch可以看作是觸發這個行為的方式,reducer則是描述如何改變數據的
    • Dva中,connect model的組件通過props可以訪問到dispatch,可以調用model中的reducer或者effects

      import { connect } from 'dva';
      const testCom = props => {
      const { dispatch } = props;
      const changeValue = (id, val) => {
         // 調用reducer,一般是同步修改state中的值
         dispatch({
           type: 'dva/save',
           payload: {
             param: val
           },
         });
         // 調用effect,一般是發送後台請求
         dispatch({
           type: 'dva/queryValue',
           payload: {
             id: id
           },
         });
       };
      return(
       <div>'hello world'</div>
      )
      }
      export default connect(({ dva }) => ({ ...dva }))(testCom);

Model中的Effects函數解析

需要注意的是:Effects裏面的函數都是Generator函數

  1. yield

    • 固定關鍵詞,Generator函數自帶的關鍵詞,和*搭配使用,有點像asyncawait,使用*則表明它是Generator函數
    • 然後每使用一個yield就是告訴程序這裏是異步,需要等待這個後面的代碼執行完成,同步代碼可不使用該關鍵詞
  2. payload

    • 頁面上通過dispatch傳過來的payload同名參數
  3. select

    • DvaEffects函數的固定傳參
    • 用於拿到modelstate的數據,需要注意的是,state後面跟命名空間namespace的值

      const data = yield select((state) => state.namespaceName.valueName);
  4. call

    • DvaEffects函數的固定傳參
    • 第一個參數是一個異步函數,payload是參數,可以通過call來執行一個完整的異步請求,又因為yield的存在,就實現了異步轉同步的方案

      const { data } = yield call(queryInterface, payload);
  5. put

    • DvaEffects函數的固定傳參
    • 可以使用同model中的Reducers或者Effects,通過Reducers來實現數據到頁面的更新,也可以通過put實現Effects的嵌套使用

      yield put({
       type: 'save',
       payload: {
         ...payload
       },
      });

開發目錄

由於公司的fish框架以及常見的umi框架都對Dva做了深度繼承,會默認將src/models下的model定義自動掛載,只需要在model文件夾中新建文件即可新增一個model用來管理組件狀態,對於某個page文件夾下面的model也會默認掛載

├─assets `靜態資源`
├─components `公共組件`
├─config `路由和環境配置`
├─constants `全局靜態常量`
├─locale `國際化`
│  ├─en_US `英文配置`
│  └─zh_CN `中文配置`
├─models `全局數據狀態` *Dva涉及的目錄*
├─pages `頁面目錄,用我參與開發的其中一個目錄來作為示例` *Dva涉及的目錄*
│  ├─NodeConfig  `NodeConfig示例目錄`
│  │  ├─components
│  │  │  ├─Select `Select組件頁面文件` *Dva涉及的目錄*
│  │  │  │  └─components
│  │  │  │      ├─AudienceInfo
│  │  │  │        │    ├─index.js
│  │  │  │        │    └─index.less
│  │  │  │      ├─BlackList
│  │  │  │        │    ├─index.js
│  │  │  │        │    └─index.less
│  │  │  │      ├─ControlGroup
│  │  │  │        │    ├─index.js
│  │  │  │        │    └─index.less
│  │  │  │      └─GroupSelect
│  │  │  │        │    ├─index.js
│  │  │  │        │    └─index.less
│  │  │  │        ├─index.js
│  │  │  │        └─index.less
│  │  ├─models
│  │  │  ├─select.js `Select組件數據狀態管理` *Dva涉及的目錄*
│  │  └─services
├─services `全局接口配置`
├─themes `全局樣式主題`
└─utils `js通用工具`

PS: 該樹形圖通過 `windows shell` 自帶的 `tree` 命令生成

如何使用Dva

首先定義一個簡易的model示例

export default {
  namespace: 'dva',
  state: {
    id: '',
    value: {},
  },
  effects: {
    // 所有effect前必須要加 *
    *queryValue({ payload }, { select, call, put }) {
      const params = {
          id: payload.id ? payload.id : yield select(state => state.select.id)
      }
      const { data } = yield call(queryInterface, params); // queryInterface是定義好的後台請求接口,一般用axios或fetch來完成
      yield put({ type: 'save', payload: data });
    },
  },
  reducers: {
    save(state, { payload }) {
      return {
        ...state,
        ...payload,
      };
    },
  },
  subscriptions: {
    keyboardWatcher({ dispatch }) {
      key('⌘+up, ctrl+up', () => { dispatch( {type:'save'}) });
    },
  },
};

然後把model和組件綁定在一起

React的Connect函數是一種柯里化寫法

import { connect } from 'dva';

const testCom = props => {
  const { helloWorld = 'hello world'} = props;
  return(
    <div>{ helloWorld }</div>
  )
}

// 綁定之後就可以在testCom組件中使用命名為dva的model了
export default connect(({ dva }) => ({ ...dva }))(testCom);

<span id="jumpCurry"></span>

柯里化

柯里化是把接受多個參數的函數轉換成接受一個單一參數的函數(PS:Scala語言中也有類似的設計)

// 柯里化
var foo = function(x) {
    return function(y) {
        return x + y
    }
}
foo(3)(4)


// 普通方法
var add = function(x, y) {
    return x + y;
}
add(3, 4) 

<span id="jumpNoState"></span>

無狀態組件

創建無狀態組件是為了創建純展示組件,這種組件只負責根據傳入的props來展示,不涉及到要改變state狀態的操作,
在實際項目中頁面組件被寫成無狀態的組件,通過簡單組合可以構建成頁面或複雜組件,通過多個簡單組件來合併成一個複雜的大應用

const NoStateComponent = props => {
  const { helloWorld = 'hello world'} = props;
  return(
    <div>{ helloWorld }</div>
  )
}
export default NoStateComponent;

無狀態組件的優點

  1. 由於是無狀態組件,所以無狀態組件就不會在有組件實例化的過程,無實例化過程也就不需要分配多餘的內存,從而性能得到一定的提升
  2. 代碼整潔、可讀性高,對於大型項目的開發維護非常有好處

參考文檔一 ———— Dva官方文檔
參考文檔二 ———— UMI官方文檔
參考文檔三 ———— REACT基礎筆記 MODEL分層
參考文檔四 ———— 前端數據流方案Dva
參考文檔五 ———— 淺析dva (史上最全的dva用法及分析)
參考文檔六 ———— 【dva】model中effects函數的解析
參考文檔七 ———— Generator 函數的詳解
參考文檔八 ———— React connect()() 雙括號 --柯里化寫法
參考文檔九 ———— 高級函數技巧-函數柯里化

我是 fx67ll.com,如果您發現本文有什麼錯誤,歡迎在評論區討論指正,感謝您的閲讀!
如果您喜歡這篇文章,歡迎訪問我的 本文github倉庫地址,為我點一顆Star,Thanks~ :)
轉發請註明參考文章地址,非常感謝!!!

user avatar huishou 頭像 tingzhong666 頭像 79px 頭像 b_a_r_a_n 頭像 pangsir8983 頭像 mapvthree 頭像 gfeteam 頭像 tofrankie 頭像 fyuanlove 頭像 yangkaiqiang 頭像 xuriliang 頭像 heptagon 頭像
28 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.