原文地址在我的博客, 轉載請注明出處,謝謝!
前言
使用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架構思路清晰,代碼書寫方式固定,有利于團隊合作,但可擴展性不強