原文地址在個人博客, 轉載請註明出處,謝謝!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架構思路清晰,代碼書寫方式固定,有利於團隊合做,但可擴展性不強