dva值得一試

原文地址在個人博客, 轉載請註明出處,謝謝!javascript

前言

使用React技術棧管理大型複雜的應用每每要使用Redux來管理應用的狀態,然而隨着深度使用,Redux也暴露出了一些問題。如編寫頁面配套(action、reducer)過於繁瑣、複雜,組件之間耦合較深、不夠扁平化、調用action creator發起動做破壞action純潔性且必須層層傳遞等。這些缺點迫使使用Redux的人開始探索好的架構方式,解決或減輕使用Redux的問題。業界標杆阿里爲此推出了dva 和 Mirror兩種改良Redux的架構方案,不過這二者相似,本文就介紹一下dva。css

概述

本文介紹了dva的產生背景,dva是什麼,用來作什麼,解決了什麼問題,使用場景,原理,實踐以及個人使用心得。html

背景

Redux 文檔中介紹,咱們須要編寫頁面的action creator來提交,須要寫reducer來更新state,最好對action 和 reducer 作頁面爲單位的分割,利用redux 給的API 構建容器組件包裹父組件來connect store拿到數據,而後再向下傳遞給functional component 來渲染,整個過程就實現了單向數據流。當應用複雜起來,通常的作法是配合react-router 作頁面分割,光這個分割,你就得 作redux store 的建立,中間件的配置,路由的初始化,Provider 的 store 的綁定,saga 的初始化,還要處理 reducer, component, saga之間的聯繫...這個沒辦法,Redux就這麼複雜;可是每一個頁面下要有本身對應的action、reducer,通常還會有saga,這樣的話每一個頁面下都要有四五個文件目錄(還有components、containers),每一個文件目錄下估計還要有不一樣功能的action、reducer、saga...若是這能忍的話,你在組件裏發起action有兩個方案,第一:調用通過層層傳遞的action creator 或者 sagas,第二,讓saga監聽action,再在組件裏直接dispatch相應action類型就好了,不用層層傳遞,可是得提早 fork -> watcher -> worker.....真的是很是複雜,容易出錯。java

dva 是什麼

dva名字取自遊戲守望先鋒裏的一個駕駛機甲的韓國英雄叫dva,大概含義就是Redux的機甲吧...react

確實,git

dva 是基於現有應用架構 (redux + react-router + redux-saga 等)的一層輕量封裝,沒有引入任何新概念,所有代碼不到 100 行。( Inspired by elm and choo. )github

dva 幫你自動化了Redux 架構一些繁瑣的步驟,好比上面所說的redux store 的建立,中間件的配置,路由的初始化等等,沒有什麼魔法,只是幫你作了redux + react-router + redux-saga 架構的那些噁心、繁瑣、容易出錯的步驟,只需寫幾行代碼就能夠實現上述步驟,它解決了背景所說的全部缺點。dva介紹json

此外,dva重要的特性就是把一個路由下的state、reducer、sagas 寫到一塊了,清晰明瞭redux

app.model({
  namespace: 'products', //分割的路由,對應要combine到root Reducer裏的名字,這裏就是state.products
  state: {  //這個路由下初始state
    list: [],
    loading: false,
  },
  subscriptions: [  //用來監聽路徑變化,這裏就是當路由爲products時dispatch一個獲取數據的請求
    setup({ dispatch, history }) {
      return history.listen(({ pathname }) => {
        if (pathname === 'products') {
          //dispatch({ type: 'getUserInfo', payload: {} });
        }
      });
    },
  },
  ],
  effects: { //saga裏的effects,裏面的各類處理異步操做的saga
    ['products/query']: function*() {
      yield call(delay(800));
      yield put({
        type: 'products/query/success',
        payload: ['ant-tool', 'roof'],
      });
    },
  },
  reducers: {  // reducers 
    ['products/query'](state) {
      return { ...state, loading: true, };
    },
    ['products/query/success'](state, { payload }) {
      return { ...state, loading: false, list: payload };
    },
  },
});複製代碼

dva的思想

官方文檔後端

dva就是把以前Redux每一個路由下的state、reducer、sagas寫到一塊去了,作了寫到一塊去也能作到之前redux能作的事,而且讓思路變得很清晰 :

每一個路由下都有一個model,這個model掌管這個路由的全部狀態(action、state、reducer、sagas),組件想改變狀態dispatch type名字就好了。

img
img

實踐

搞懂框架的腳手架是快速上手這個框架的一個好方法,下面是dva-cli

項目架構

.
├── src                    
    ├── assets             # 圖片、logo
    ├── components         # 公用UI組件
    ├── index.css          # CSS for entry file
    ├── index.html         # HTML for entry file
    ├── index.js           # 入口文件
    ├── models             # 這裏存放的就是上面說的dva的model,最好每一個路由一個model
    ├── router.js          # 路由文件
    ├── routes             # 路由組件,跟Redux相同
    ├── services           # 每一個頁面的services,一般是獲取後端數據的接口定義
    └── utils              # 存放一些工具
        └── request.js     # 這裏封裝一個用來與後端通訊的接口
├── .editorconfig          #
├── .eslintrc              # Eslint config
├── .gitignore             #
├── .roadhogrc             # Roadhog config
└── package.json           #複製代碼

按照dva的架構,每一個路由下都有個model層,在model定義好這個路由的initialstate、reducers、sagas、subscriptions;而後connect組件,當在組件裏發起action時,直接dispatch就好了,dva會幫你自動調用sagas/reducers。當發起同步action時,type寫成'(namespace)/(reducer)'dva就幫你調用對應名字的reducer直接更新state,當發起異步action,type就寫成'(namespace)/(saga)',dva就幫你調用對應名字的saga異步更新state,很是方便:

在組件裏:

...
  const { dispatch } = this.props
  dispatch({
    type: 'namespace/sagas', //這裏的type規範爲model裏面定義的namespace和effects下面定義的sagas或者 
    payload: {               // reducers,這樣就能實現自動調用這些函數
      ...
    }
  })複製代碼

注意,dispatch用來更新state某個數據後,下一步從state拿到的這個數據並非更新後的:

...
  const { dispatch, data } = this.props
  dispatch({
    type: 'namespace/sagas', //這裏的type規範爲model裏面定義的namespace和effects下面定義的sagas或者 
    payload: {               // reducers,這樣就能實現自動調用這些函數
      data      //這裏想更新data
    }
  })
  console.log(data) // 仍然是以前的數據,並非dispatch更新後的數據
                      // 由於dispatch是異步的,如同React的setState後面打印state複製代碼

此外,因爲不用層層傳遞action creator,mapDispatchToProps就不用再寫了,組件之間的耦合度也下降了,或者說根本沒有關係了,dva使組件之間的關係變得更加扁平化,沒有什麼父子、兄弟關係,這樣組件就具備很高的可重用性。全部須要在組件裏通訊的數據都要放在state中,而後connect組件,只拿到組件關心的數據,就像這樣:

class App extends Component {
  ...
}

function mapStateToProps(state) {
  const {
    data
  } = state.user;  // user 對應namespace
  const loading = state.loading.effects['user/fetch'];
  return {
    data,
    loading
  };
}
export default connect(mapStateToProps)(User);複製代碼

這樣寫,除了具備很高的重用性,也避免了父組件更新,子組件也會隨之更新的缺點了!只要這個組件關心的數據沒變,它就不會從新渲染,省掉了重寫shouldComponentUpdate來提升性能,邏輯也變得清晰、簡單起來!

另外,model下有個subscriptions用於訂閱一個數據源,能夠在這裏面監聽路由變化,好比當路由跳轉到本頁面時,發起請求來獲取初始數據:

subscriptions: {
    setup: ({ history, dispatch }) => history.listen(({ pathname, query }) => {
      if (pathname === '/user') {
        dispatch({
          type: 'fetch',
          payload: {
            query
          }
        });
      }
    }),
  },
};複製代碼

問題

使用沒多久,瞭解較淺,暫時沒發現什麼問題

總結

dva框架封裝了Redux 架構一些繁瑣、複雜的步驟和經常使用庫,使用dva,不會構建Redux架構也能夠,dva幫你作好了;

dva 下降了組件之間的耦合度,沒有父子、兄弟組件的關係,提升了組件可重用性以及渲染性能,使思路變得簡單清晰;

dva架構思路清晰,代碼書寫方式固定,有利於團隊合做,但可擴展性不強

相關文章
相關標籤/搜索