[TOC]
# dva
[dva](https://github.com/dvajs/dva) 首先是一個基于現有應用架構 [redux](https://github.com/reduxjs/redux) 和 [redux-saga](https://github.com/redux-saga/redux-saga) 的數據流方案,然后為了簡化開發體驗,dva 還額外內置了 [react-router](https://github.com/ReactTraining/react-router) 和 [fetch](https://github.com/github/fetch) ,所以也可以理解為一個輕量級的應用框架,沒有引入任何新概念。
1. 腳手架工具:[dva-cli](https://github.com/dvajs/dva-cli)
2. [dva-知識地圖](https://github.com/dvajs/dva-knowledgemap/blob/29549f253ad130802146b6d066b3ad1af4c7d0be/README.md)
## 整體架構
分析一下這個圖:

首先我們根據 `url` 訪問相關的 `Route-Component`,在組件中我們通過 `dispatch` 發送 `action` 到 `model` 里面的 `effect` 或者直接 `Reducer`;
當我們將`action`發送給`effect`,基本上是取服務器上面請求數據的,服務器返回數據之后,`effect` 會發送相應的 `action` 給 `reducer`,由**唯一能操作** `state` 的 `reducer` 產生新的 `state` ,然后通過`connect` 重新渲染組件。
當我們將`action`發送給`reducer`,那直接由 `reducer` 產生新的 `state`,然后通過 `connect` 重新渲染組件。
這樣我們就能走完一個流程了。
## Model
`model` 是 `dva` 中最重要的概念,`Model` 非 `MVC` 中的 `M`,而是領域模型,用于把數據相關的邏輯聚合到一起,幾乎所有的數據,邏輯都在這里進行處理分發
### state
State 表示 Model 的狀態數據,通常表現為一個 javascript 對象(當然它可以是任何值);操作的時候每次都要當作不可變數據(immutable data)來對待,保證每次都是全新對象,沒有引用關系,這樣才能保證 State 的獨立性,便于測試和追蹤變化。
在 dva 中你可以通過 dva 的實例屬性?`_store`?看到頂部的 state 數據,但是通常你很少會用到:
```js
const app = dva();
console.log(app._store); // 頂部的 state 數據
```
### namespace
`model` 的命名空間,同時也是它在 全局 `state` 上的屬性(作為一個大對象的鍵值),只能用字符串,我們發送在發送 `action` 到相應的 `reducer` 時,就會需要用到 `namespace` 。
### dispatch 函數
`type dispatch = (a: Action) => Action`
dispatching function 是一個用于觸發 action 的函數,action 是改變 State 的唯一途徑,但是它只描述了一個行為,而 dipatch 可以看作是觸發這個行為的方式,而 Reducer 則是描述如何改變數據的。
在 dva 中,`connect`( react-redux 的 connect) Model 的組件通過 props 可以訪問到 dispatch,可以調用 Model 中的 Reducer 或者 Effects,常見的形式如:
```js
// 返回 Promise
dispatch({
type: 'user/add', // 如果在 model 外調用,需要添加 namespace
payload: {}, // 需要傳遞的信息
});
```
[Dva異步的同步處理](https://www.jianshu.com/p/c11fd2e10f0b)
### reducers
以`key/value` 格式定義 `reducer`,用于處理同步操作,唯一可以修改 `state` 的地方。由 `action` 觸發。其實一個純函數。
在 dva 中,reducers 聚合積累的結果是當前 model 的 state 對象。通過 actions 中傳入的值,與當前 reducers 中的值進行運算獲得新的值(也就是新的 state)。
需要注意的是 Reducer 必須是[純函數](https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch3.md),所以同樣的輸入必然得到同樣的輸出,它們不應該產生任何副作用。
并且,每一次的計算都應該使用[immutable data](https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch3.md#reasonable),這種特性簡單理解就是每次操作都是返回一個全新的數據(獨立,純凈),所以熱重載和時間旅行這些功能才能夠使用。
**如果 reducers中的同步方法名和 effects 中的異步方法名 相同,那么都會被調用,且 優先執行 reducers,應該避免同名!**
### effects
用于處理異步操作和業務邏輯,不直接修改 `state`,簡單的來說,就是獲取從服務端獲取數據,并且發起一個 `action` 交給 `reducer` 的地方。
其中它用到了 redux-saga,里面有幾個常用的函數。
```
*add(action, { call, put }) {
yield call(delay, 1000); // 執行異步函數
yield put({ type: 'minus' }); // 發出一個 Action,類似于 dispatch
},
```
### subscription
Subscriptions 是一種從?源?獲取數據的方法,它來自于 elm。
Subscription 語義是訂閱,用于訂閱一個數據源,然后根據條件 `dispatch` 需要的 action。數據源可以是當前的時間、服務器的 websocket 連接、keyboard 輸入、geolocation 變化、history 路由變化等等。
```js
import key from 'keymaster';
...
app.model({
namespace: 'count',
subscriptions: {
keyEvent(dispatch) {
key('?+up, ctrl+up', () => { dispatch({type:'add'}) });
},
}
});
```
## Router
這里的路由通常指的是前端路由,由于我們的應用現在通常是單頁應用,所以需要前端代碼來控制路由邏輯,通過瀏覽器提供的?[History API](http://mdn.beonex.com/en/DOM/window.history.html)?可以監聽瀏覽器url的變化,從而控制路由相關操作。
dva 實例提供了 router 方法來控制路由,使用的是 [react-router](https://github.com/reactjs/react-router)。
```js
import { Router, Route } from 'dva/router';
app.router(({history}) =>
<Router history={history}>
<Route path="/" component={HomePage} />
</Router>
);
```
## Route Components
在[組件設計方法](https://github.com/dvajs/dva-docs/blob/master/v1/zh-cn/tutorial/04-%E7%BB%84%E4%BB%B6%E8%AE%BE%E8%AE%A1%E6%96%B9%E6%B3%95.md)中,我們提到過 Container Components,在 dva 中我們通常將其約束為 Route Components,因為在 dva 中我們通常以頁面維度來設計 Container Components。
所以在 dva 中,通常需要 connect Model的組件都是 Route Components,組織在`/routes/`目錄下,而`/components/`目錄下則是純組件(Presentational Components)。
## dva-loading
每個頁面中將loading狀態作為屬性傳入組件,在進行樣式處理,比如轉圈圈或者顯示正在加載什么的,但是重點是,我們的app有多個頁面,每個頁面都這么做,很繁瑣。
dva 有一個管理 effects 執行的 hook,并基于此封裝了 [dva-loading 插件](https://github.com/dvajs/dva/tree/master/packages/dva-loading)。通過這個插件,我們可以不必一遍遍地寫`showLoading`和`hideLoading`,當發起請求時,插件會自動設置數據里的 loading 狀態為 true 或 false 。然后我們在渲染 components 時綁定并根據這個數據進行渲染。
`umi-plugin-dva` 默認內置了 dva-loading 插件。
dva-loading的使用非常簡單,在index.js中加入:
```
import createLoading from 'dva-loading';
const app = dva();
app.use(createLoading());
```
loading 在異步請求發出那一刻會持續監聽該異步請求方法的狀態,在異步請求結束之前 isLoading 的值一直是 true,當此次異步請求結束時 isLoading 的值變成 false,同時 loading 對象停止監聽。
如何只做一次狀態處理,每次請求期間都會觸發loading狀態呢,其實也很簡單啦,因為dva-loading提供了一個global屬性。
```
loading: {
global: true,
effects: { 'user/query': true },
models: { 'user/query', true },
}
/*
'user/query' 是單一異步請求后臺的方法,在請求過程中,global 和 effects 中值都為 true,
當請求結束,已經有數據返回,userList 中獲取到用戶列表時該值為 false。
可能有個疑問:既然 global 可以展示異步加載是否完成,為什么還要 effects 屬性,這是因為一個頁面中可能同時有多個異步加載,只要有一個異步加載沒有完成,global 都是 true。
*/
```
如果同時發出若干個異步請求,需求是當所有異步請求都響應才做下一步操作,可以使用`loading.global()`方法,該方法監聽所有異步請求的狀態。
具體參考這個[Commit](https://github.com/umijs/umi-dva-user-dashboard/commit/f81d36c639d35e67db8b2d0a65b561e4af203eb1)。
## 參考
[dva理論到實踐——幫你掃清dva的知識盲點](https://www.jianshu.com/p/e184cd6d253c)
===好用真舒服,感覺起飛了===