dva值得一試
原文地址在我的博客, 轉載請註明出處,謝謝!
前言
使用React技術棧管理大型複雜的應用往往要使用Redux來管理應用的狀態,然而隨著深度使用,Redux也暴露出了一些問題。如編寫頁面配套(action、reducer)過於繁瑣、複雜,組件之間耦合較深、不夠扁平化、調用action creator發起動作破壞action純潔性且必須層層傳遞等。這些缺點迫使使用Redux的人開始探索好的架構方式,解決或減輕使用Redux的問題。業界標杆阿里為此推出了dva 和 Mirror兩種改良Redux的架構方案,不過這兩者類似,本文就介紹一下dva。
概述
本文介紹了dva的產生背景,dva是什麼,用來做什麼,解決了什麼問題,使用場景,原理,實踐以及我的使用心得。
背景
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.....真的是非常複雜,容易出錯。
dva 是什麼
dva名字取自遊戲守望先鋒里的一個駕駛機甲的韓國英雄叫dva,大概含義就是Redux的機甲吧...
確實,
dva 是基於現有應用架構 (redux + react-router + redux-saga 等)的一層輕量封裝,沒有引入任何新概念,全部代碼不到 100 行。( Inspired by elm and choo. )
dva 幫你自動化了Redux 架構一些繁瑣的步驟,比如上面所說的redux store 的創建,中間件的配置,路由的初始化等等,沒有什麼魔法,只是幫你做了redux + react-router + redux-saga 架構的那些噁心、繁瑣、容易出錯的步驟,只需寫幾行代碼就可以實現上述步驟,它解決了背景所說的所有缺點。dva介紹
此外,dva重要的特性就是把一個路由下的state、reducer、sagas 寫到一塊了,清晰明了
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名字就行了。
實踐
搞懂框架的腳手架是快速上手這個框架的一個好方法,下面是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架構思路清晰,代碼書寫方式固定,有利於團隊合作,但可擴展性不強
推薦閱讀:
※react許可證的問題是否意味著要轉技術棧了?
※讓你的Web項目好看起來!
※vue移動端框架到底哪家強
※Vue.js 的組件化編程
※angular的$watch是如何實現的?