Dva是什么
dva 是基于現(xiàn)有應(yīng)用架構(gòu) (redux + react-router + redux-saga 等)的一層輕量封裝,沒有引入任何新概念,全部代碼不到 100 行。( Inspired by elm and choo. )
- 框架,而非類庫
- 基于redux, react-router, redux-soga的輕量級封裝
- 借鑒elm的概念, Reducer, Effect和Subscription
- ...
可以說,dva是基于react+redux最佳實(shí)踐上實(shí)現(xiàn)的封裝方案,簡化了redux和redux-saga使用上的諸多繁瑣操作。
文件結(jié)構(gòu)
官方推薦的:
├── /mock/ # 數(shù)據(jù)mock的接口文件
├── /src/ # 項(xiàng)目源碼目錄
│ ├── /components/ # 項(xiàng)目組件
│ ├── /routes/ # 路由組件(頁面維度)
│ ├── /models/ # 數(shù)據(jù)模型
│ ├── /services/ # 數(shù)據(jù)接口
│ ├── /utils/ # 工具函數(shù)
│ ├── route.js # 路由配置
│ ├── index.js # 入口文件
│ ├── index.less
│ └── index.html
├── package.json # 定義依賴的pkg文件
└── proxy.config.js # 數(shù)據(jù)mock配置文件
初體驗(yàn)
一個小Demo
5個API
app = dva(Opts)app.use(Hooks)app.models(ModelObject)app.router(Function)app.start([HTMLElement])
8個概念
- State
- Action
- Model
- Reducer
- Effect
- Subscription
- Router
- RouteComponent
數(shù)據(jù)流向

數(shù)據(jù)的改變發(fā)生通常是通過:
- 用戶交互行為(用戶點(diǎn)擊按鈕等)
- 瀏覽器行為(如路由跳轉(zhuǎn)等)觸發(fā)的
當(dāng)此類行為會改變數(shù)據(jù)的時候可以通過 dispatch 發(fā)起一個 action,如果是同步行為會直接通過 Reducers 改變 State ,如果是異步行為(副作用)會先觸發(fā) Effects 然后流向 Reducers 最終改變 State 。
所以在 dva 中,數(shù)據(jù)流向非常清晰簡明,并且思路基本跟開源社區(qū)保持一致。如上圖。
相關(guān)概念與理解
定義model
namespace: 'monthCard',
state: {
list: [],
total: 0,
editData: {},
loading: false
},
subscriptions: {
setup({ dispatch, history}){
}
},
effects: {},
reducers: {}
State是整個應(yīng)用的數(shù)據(jù)層。應(yīng)用的state被存儲在一個object tree中。應(yīng)用的初始state在model中定義,也就是說,由model state組成全局state。操作的時候每次都要當(dāng)作不可變數(shù)據(jù)(immutable data)來對待,保證每次都是全新對象,沒有引用關(guān)系,這樣才能保證 State 的獨(dú)立性,便于測試和追蹤變化。
namespace是model state在全局state中所用到的key。
這里的Model非MVC中的M,是用于把數(shù)據(jù)相關(guān)的邏輯聚合到一起。
完成component
const monthCardPrice = ({
monthCard,
monthCard: {
list
},
diapacth
}) => {
const queryList = () => {
dispatch({
type: `monthCard/query`,
payload: {}//需要傳遞的數(shù)據(jù)
})
}
return (
<div>
<button onclick={queryList}>查詢</button>
</div>
)
}
const mapStateToProps = ({
monthCard,
global
}) => {
return {
monthCard,
global
}
}
export default connect(mapStateToProps)(monthCardPrice)
RouteComponent 表示 Router 里匹配路徑的 Component,通常會綁定 model 的數(shù)據(jù)
Presentational Component是獨(dú)立的純粹的,例如ant.design UI組件的react實(shí)現(xiàn),每個組件跟業(yè)務(wù)數(shù)據(jù)并沒有耦合關(guān)系,只是完成自己獨(dú)立的任務(wù),需要的數(shù)據(jù)通過 props 傳遞進(jìn)來,需要操作的行為通過接口暴露出去。 而 Container Component 更像是狀態(tài)管理器,它表現(xiàn)為一個容器,訂閱子組件需要的數(shù)據(jù),組織子組件的交互邏輯和展示。
所以在 dva 中,通常需要 connect Model的組件都是 Route Components,組織在/routes/目錄下,而/components/目錄下則是純組件(Presentational Components)。
dva架構(gòu)里component中基本不需要用到state。
綁定數(shù)據(jù)
這里利用es6結(jié)構(gòu)賦值通過props將參數(shù)傳入組件。monthCard對應(yīng)model上的state,通過connect來綁定綁定model state。這里connect來自react-redux。意味著Component里可以拿到Model中定義的數(shù)據(jù),Model中也能接收到Component里dispatch的action。實(shí)現(xiàn)了Model和Component的連接。Action
Action表示操作事件,可以是同步,也可以是異步。Action 是一個普通 javascript 對象,它是改變 State 的唯一途徑。無論是從 UI 事件、網(wǎng)絡(luò)回調(diào),還是 WebSocket 等數(shù)據(jù)源所獲得的數(shù)據(jù),最終都會通過 dispatch 函數(shù)調(diào)用一個 action,從而改變對應(yīng)的數(shù)據(jù)。
dispatch 函數(shù),通過 type 屬性指定對應(yīng)的 actions 類型,而這個類型名在 reducers(effects)會一一對應(yīng),從而知道該去調(diào)用哪一個 reducers(effects),除了 type 以外,其它對象中的參數(shù)隨意定義,都可以在對應(yīng)的 reducers(effects)中獲取,從而實(shí)現(xiàn)消息傳遞,將最新的數(shù)據(jù)傳遞過去更新 model 的數(shù)據(jù)(state)
注意:Action在model自身模型以外定義時需要加model的namespace前綴, 在model中定義不需要加。
- 添加樣式:
CSS Modules會給組件的className加上hash字符串,來保證className僅對引用了樣式的組件有效,如styles.normal可能會輸出為normal___39QwY。
className的輸出格式可以通過webpack.config進(jìn)行修改。
更新state
namespace: 'monthCard',
state: {
list: [],
total: 0,
editData: {},
loading: false
},
subscriptions: {},
effects: {},
reducers: {
+ updateState(state, action){
+ return {
+ ...state,
+ ...action.payload
+ }
+ }
}
reducer 是唯一可以更新 state 的地方,這個唯一性讓我們的 App 更具可預(yù)測性,所有的數(shù)據(jù)修改都有據(jù)可查。reducer 是 pure function,他接收參數(shù) state 和 action,返回新的 state,通過語句表達(dá)即 (state, action) => newState。該函數(shù)把一個集合歸并成一個單值。
Reducer 的概念來自于是函數(shù)式編程,在 dva 中,reducers 聚合積累的結(jié)果是當(dāng)前 model 的 state 對象。通過 actions 中傳入的值,與當(dāng)前 reducers 中的值進(jìn)行運(yùn)算獲得新的值(也就是新的 state)。
注意:Reducer函數(shù)必須是純函數(shù)。
Effects異步處理
+ //異步請求
//request 是我們封裝的一個網(wǎng)絡(luò)請求庫
+ async function queryFromService(data) {
+ return request("queryFromApi", {
+ data,
+ method: "post",
+ dataType: "payload"
+ })
+ }
namespace: 'monthCard',
state: {
list: [],
total: 0,
editData: {},
loading: false
},
subscriptions: {},
effects: {
+ * query({ payload }, { call, put }){
+ yield put ({
+ type: `updateState`,
+ payload: {
+ loading: true
+ }
+ })
+
+ const { data } = yield call (queryFromService, parse(payload))
+
+ if(data){
+ yield put({
+ type: 'querySuccess',
+ payload: {
+ loading: false,
+ list: data.data,
+ editData: data.data,
+ total: data.recordsTotal
+ }
+ })
+ } else {
+ yield put({
+ type: 'querySuccess',
+ payload: {
+ data: {},
+ loading: false
+ }
+ })
+ }
+ },
+ * create(){},
+ * 'delete'(){},//delete是關(guān)鍵字
+ * update(){}
},
reducers: {
updateState(state, action){
return {
...state,
...action.payload
}
},
+ querySuccess(state, action){
+ if(action.payload.data){
+ const {
+ data: {
+ status,
+ message
+ }
+ } = action.payload
+
+ if(1 == status){
+ //
+ }else {
+ Message.error(message)
+ }
+ }
+
+ return {
+ ...state
+ }
+ }
}
當(dāng)數(shù)據(jù)需要從服務(wù)器獲取時,需要發(fā)起異步請求,請求到數(shù)據(jù)之后,通過調(diào)用 Reducers更新數(shù)據(jù)到全局state。dva 通過對 model 增加 effects 屬性來處理 side effect(異步任務(wù)),這是基于 redux-saga 實(shí)現(xiàn)的,語法為 generator。Generator 返回的是迭代器,通過 yield 關(guān)鍵字實(shí)現(xiàn)暫停功能。Redux-saga 中文文檔。
* query(action, {call, put, select}){}表示一個worker Saga,監(jiān)聽所有的query action,并觸發(fā)一個Api調(diào)用以獲取服務(wù)器數(shù)據(jù)。當(dāng)每個query action被發(fā)起時調(diào)用 call 和 put 都是 redux-saga 的 effects,call 表示調(diào)用異步函數(shù),put 表示 dispatch action,其他的還有 select, take, fork, cancel 等,select 則可以用來訪問其它 model。格式:*(action, effects) => void
在Effects里,Generator函數(shù)通過yield命令將異步操作同步化,無論是yield 亦或是 async 目的只有一個: 讓異步編寫跟同步一樣 ,從而能夠很好的控制執(zhí)行流程。
訂閱數(shù)據(jù)源
subscriptions: {
setup(( dispatch, history )){
history.listen(({ pathname, query }) => {
if(pathToRegexp(`/y/monthCard/list`).test(pathname)) {
dispatch({
type:`query`
})
}
})
}
}
Subscriptions 表示訂閱,用于訂閱一個數(shù)據(jù)源,然后按需 dispatch action。格式為 ({ dispatch, history }) => unsubscribe 。比如:當(dāng)用戶進(jìn)入 /y/monthCard/list 頁面時,觸發(fā) action query 加載數(shù)據(jù)。
如果 url 規(guī)則比較復(fù)雜,比如 /users/:userId/search,那么匹配和 userId 的獲取都會比較麻煩。這是推薦用 path-to-regexp 簡化這部分邏輯。
定義路由
路由決定進(jìn)入url渲染哪些Component。history 默認(rèn)是 hashHistory 并且?guī)в?_k 參數(shù),可以換成 browserHistory,也可以通過配置去掉 _k 參數(shù)。
工具
dva使用總結(jié)
dva將所有與數(shù)據(jù)操作相關(guān)的邏輯集中放在一個地方處理和維護(hù),在數(shù)據(jù)跟業(yè)務(wù)狀態(tài)交互比較緊密的場景下,會使我們的代碼更加清晰可控。尤其適用于數(shù)據(jù)跟業(yè)務(wù)狀態(tài)關(guān)聯(lián)性極強(qiáng)的企業(yè)級后臺信息管理系統(tǒng)。
對于一個企業(yè)級后臺管理系統(tǒng),由于要進(jìn)行大量的數(shù)據(jù)操作,在設(shè)計(jì)model時將不同類型的業(yè)務(wù)需求數(shù)據(jù)操作分開處理,便于維護(hù)。
項(xiàng)目的開發(fā)流程一般是從設(shè)計(jì)model state開始進(jìn)行抽象數(shù)據(jù),完成component后,將組件和model建立關(guān)聯(lián),通過dispatch一個action,在reducer中更新數(shù)據(jù)完成數(shù)據(jù)同步處理;當(dāng)需要從服務(wù)器獲取數(shù)據(jù)時,通過Effects數(shù)據(jù)異步處理,然后調(diào)用Reducer更新全局state。是一個單向的數(shù)據(jù)流動過程。解決了redux中代碼分散和重寫問題,總之,Dva:Build redux application easier and better。
學(xué)習(xí)資料
官方地址
Redux-saga 中文文檔
dva-knowledgemap
dva: react application arch in ant financial