原文地址在個人博客, 轉載請註明出處,謝謝!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,大概含義就是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就是把以前Redux每一個路由下的state、reducer、sagas寫到一塊去了,作了寫到一塊去也能作到之前redux能作的事,而且讓思路變得很清晰 :
每一個路由下都有一個model,這個model掌管這個路由的全部狀態(action、state、reducer、sagas),組件想改變狀態dispatch type名字就好了。
搞懂框架的腳手架是快速上手這個框架的一個好方法,下面是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架構思路清晰,代碼書寫方式固定,有利於團隊合做,但可擴展性不強