#### 壹、 dva的總地址:https://github.com/dvajs/dva/blob/master/README_zh-CN.md### 貳、 dva的8個概念:https://github.com/dvajs/dva/blob/master/docs/Concepts_zh-CN.md#### 1、數據流向 數據的改變發生一般是經過用戶交互行爲或者瀏覽器行爲(如路由跳轉等)觸發的,當此類行爲會改變數據的時候能夠經過 dispatch 發起一個 action,若是是同步行爲會直接經過 Reducers 改變 State ,若是是異步行爲(反作用)會先觸發 Effects 而後流向 Reducers 最終改變 State,因此在 dva 中,數據流向很是清晰簡明,而且思路基本跟開源社區保持一致(也是來自於開源社區)。![圖片描述](attimg://article/content/picture/201803/04/151308p8uj0a89g8djr7u7.png)#### 2、Models一、Statetype State = anyState 表示 Model 的狀態數據,一般表現爲一個 javascript 對象(固然它能夠是任何值);操做的時候每次都要看成不可變數據(immutable data)來對待,保證每次都是全新對象,沒有引用關係,這樣才能保證 State 的獨立性,便於測試和追蹤變化。在 dva 中你能夠經過 dva 的實例屬性 \_store 看到頂部的 state 數據,可是一般你不多會用到:```html:runconst app = dva();console.log(app._store); // 頂部的 state 數據```二、Actiontype AsyncAction = anyAction 是一個普通 javascript 對象,它是改變 State 的惟一途徑。不管是從 UI 事件、網絡回調,仍是 WebSocket 等數據源所得到的數據,最終都會經過 dispatch 函數調用一個 action,從而改變對應的數據。action 必須帶有 type 屬性指明具體的行爲,其它字段能夠自定義,若是要發起一個 action 須要使用 dispatch 函數;須要注意的是 dispatch 是在組件 connect Models之後,經過 props 傳入的。```htmldispatch({ type: 'add',});```三、dispatch 函數```htmltype dispatch = (a: Action) => Action```dispatching function 是一個用於觸發 action 的函數,action 是改變 State 的惟一途徑,可是它只描述了一個行爲,而 dipatch 能夠看做是觸發這個行爲的方式,而 Reducer 則是描述如何改變數據的。在 dva 中,connect Model 的組件經過 props 能夠訪問到 dispatch,能夠調用 Model 中的 Reducer 或者 Effects,常見的形式如:```htmldispatch({ type: 'user/add', // 若是在 model 外調用,須要添加 namespace payload: {}, // 須要傳遞的信息});```四、Reducer```html:runtype Reducer<S, A> = (state: S, action: A) => S```Reducer(也稱爲 reducing function)函數接受兩個參數:以前已經累積運算的結果和當前要被累積的值,返回的是一個新的累積結果。該函數把一個集合歸併成一個單值。Reducer 的概念來自因而函數式編程,不少語言中都有 reduce API。如在 javascript 中:```html[{x:1},{y:2},{z:3}].reduce(function(prev, next){ return Object.assign(prev, next);})//return {x:1, y:2, z:3}```在 dva 中,reducers 聚合積累的結果是當前 model 的 state 對象。經過 actions 中傳入的值,與當前 reducers 中的值進行運算得到新的值(也就是新的 state)。須要注意的是 Reducer 必須是純函數,因此一樣的輸入必然獲得一樣的輸出,它們不該該產生任何反作用。而且,每一次的計算都應該使用immutable data,這種特性簡單理解就是每次操做都是返回一個全新的數據(獨立,純淨),因此熱重載和時間旅行這些功能纔可以使用。五、EffectEffect 被稱爲反作用,在咱們的應用中,最多見的就是異步操做。它來自於函數編程的概念,之因此叫反作用是由於它使得咱們的函數變得不純,一樣的輸入不必定得到一樣的輸出。dva 爲了控制反作用的操做,底層引入了redux-sagas作異步流程控制,因爲採用了generator的相關概念,因此將異步轉成同步寫法,從而將effects轉爲純函數。至於爲何咱們這麼糾結於 純函數,若是你想了解更多能夠閱讀Mostly adequate guide to FP,或者它的中文譯本JS函數式編程指南。六、SubscriptionSubscriptions 是一種從 源 獲取數據的方法,它來自於 elm。Subscription 語義是訂閱,用於訂閱一個數據源,而後根據條件 dispatch 須要的 action。數據源能夠是當前的時間、服務器的 websocket 鏈接、keyboard 輸入、geolocation 變化、history 路由變化等等。```htmlimport key from 'keymaster';...app.model({ namespace: 'count', subscriptions: { keyEvent(dispatch) { key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) }); }, }});```#### 3、Router這裏的路由一般指的是前端路由,因爲咱們的應用如今一般是單頁應用,因此須要前端代碼來控制路由邏輯,經過瀏覽器提供的 History API 能夠監聽瀏覽器url的變化,從而控制路由相關操做。dva 實例提供了 router 方法來控制路由,使用的是react-router。```htmlimport { Router, Route } from 'dva/router';app.router(({history}) => <Router history={history}> <Route path="/" component={HomePage} /> </Router>);```#### 4、Route Components在組件設計方法中,咱們提到過 Container Components,在 dva 中咱們一般將其約束爲 Route Components,由於在 dva 中咱們一般以頁面維度來設計 Container Components。因此在 dva 中,一般須要 connect Model的組件都是 Route Components,組織在/routes/目錄下,而/components/目錄下則是純組件(Presentational Components)。### 叄、 dva的6個API:https://github.com/dvajs/dva/blob/master/docs/API_zh-CN.md#### 1、app = dva(opts),建立應用,返回 dva 實例。(注:dva 支持多實例)```htmlconst app = dva({ history, initialState, onError, onAction, onStateChange, onReducer, onEffect, onHmr, extraReducers, extraEnhancers,});```#### 2、app.use(hooks),配置 hooks 或者註冊插件。(插件最終返回的是 hooks )#### 3、app.model(model),註冊 model,詳見 #Model 部分。#### 4、app.unmodel(namespace)取消 model 註冊,清理 reducers, effects 和 subscriptions。#### 5、app.router(({ history, app }) => RouterConfig),註冊路由表。```htmlapp.router(({history}) => { return ( <Router history={history}> <Route path="/" component={App}/> <Router> )})```#### 6、app.start(selector?),啓動應用,selector 可選。 #### 另附1:Model。model 是 dva 中最重要的概念。如下是典型的例子:```html:runapp.model({ namespace: 'todo', state: [], reducers: { add(state, { payload: todo }) { // 保存數據到 state return [...state, todo]; }, }, effects: { *save({ payload: todo }, { put, call }) { // 調用 saveTodoToServer,成功後觸發 `add` action 保存到 state yield call(saveTodoToServer, todo); yield put({ type: 'add', payload: todo }); }, }, subscriptions: { setup({ history, dispatch }) { // 監聽 history 變化,當進入 `/` 時觸發 `load` action return history.listen(({ pathname }) => { if (pathname === '/') { dispatch({ type: 'load' }); } }); }, },}); ```#### 另附2:model 包含 5 個屬性。(1)namespace:model 的命名空間,同時也是他在全局 state 上的屬性,只能用字符串,不支持經過 . 的方式建立多層命名空間。(2)state:初始值,優先級低於傳給 dva() 的 opts.initialState。好比:```html:runconst app = dva({ initialState: { count: 1 },});app.model({ namespace: 'count', state: 0,});```此時,在 app.start() 後 state.count 爲 1 。(3)reducers 以 key/value 格式定義 reducer。用於處理同步操做,惟一能夠修改 state 的地方。由 action 觸發。 格式爲 (state, action) => newState 或 [(state, action) => newState, enhancer]。(4)effects以 key/value 格式定義 effect。用於處理異步操做和業務邏輯,不直接修改 state。由 action 觸發,能夠觸發 action,能夠和服務器交互,能夠獲取全局 state 的數據等等。格式爲 *(action, effects) => void 或 [*(action, effects) => void, { type }]。(5)subscriptions以 key/value 格式定義 subscription。subscription 是訂閱,用於訂閱一個數據源,而後根據須要 dispatch 相應的 action。在 app.start() 時被執行,數據源能夠是當前的時間、服務器的 websocket 鏈接、keyboard 輸入、geolocation 變化、history 路由變化等等。格式爲 ({ dispatch, history }, done) => unlistenFunction。注意:若是要使用 app.unmodel(),subscription 必須返回 unlisten 方法,用於取消數據訂閱。### 肆、 dva的知識地圖:https://github.com/dvajs/dva-knowledgemap#### 1、JavaScript 語言一、變量聲明:const 和 let,不要用 var,而是用 const 和 let,分別表示常量和變量。不一樣於 var 的函數做用域,const 和 let 都是塊級做用域。```htmlconst DELAY = 1000;let count = 0;count = count + 1;```二、模板字符串:模板字符串提供了另外一種作字符串組合的方法。```htmlconst user = 'world';console.log(`hello ${user}`); // hello world// 多行const content = ` Hello ${firstName}, Thanks for ordering ${qty} tickets to ${event}.`;```三、默認參數```htmlfunction logActivity(activity = 'skiing') { console.log(activity);}logActivity(); // skiing```四、箭頭函數:函數的快捷寫法,不須要經過 function 關鍵字建立函數,而且還能夠省略 return 關鍵字。同時,箭頭函數還會繼承當前上下文的 this 關鍵字。好比:```html[1, 2, 3].map(x => x + 1); // [2, 3, 4]```等同於:```html[1, 2, 3].map((function(x) { return x + 1;}).bind(this));```五、模塊的 Import 和 Export:import 用於引入模塊,export 用於導出模塊。好比:```html// 引入所有import dva from 'dva';// 引入部分import { connect } from 'dva';import { Link, Route } from 'dva/router';// 引入所有並做爲 github 對象import * as github from './services/github';// 導出默認export default App;// 部分導出,需 import { App } from './file'; 引入export class App extend Component {};```六、ES6 對象和數組析構賦值,析構賦值讓咱們從 Object 或 Array 裏取部分數據存爲變量。```html// 對象const user = { name: 'guanguan', age: 2 };const { name, age } = user;console.log(`${name} : ${age}`); // guanguan : 2// 數組const arr = [1, 2];const [foo, bar] = arr;console.log(foo); // 1```咱們也能夠析構傳入的函數參數。```htmlconst add = (state, { payload }) => { return state.concat(payload);};```析構時還能夠配 alias,讓代碼更具備語義。```htmlconst add = (state, { payload: todo }) => { return state.concat(todo);};```對象字面量改進,這是析構的反向操做,用於從新組織一個 Object 。```htmlconst name = 'duoduo';const age = 8;const user = { name, age }; // { name: 'duoduo', age: 8 }```定義對象方法時,還能夠省去 function 關鍵字。```htmlapp.model({ reducers: { add() {} // 等同於 add: function() {} }, effects: { *addRemote() {} // 等同於 addRemote: function*() {} },});```Spread Operator,Spread Operator 即 3 個點 ...,有幾種不一樣的使用方法。可用於組裝數組。```htmlconst todos = ['Learn dva'];[...todos, 'Learn antd']; // ['Learn dva', 'Learn antd']```也可用於獲取數組的部分項。```htmlconst arr = ['a', 'b', 'c'];const [first, ...rest] = arr;rest; // ['b', 'c']// With ignoreconst [first, , ...rest] = arr;rest; // ['c']```還可收集函數參數爲數組。```htmlfunction directions(first, ...rest) { console.log(rest);}directions('a', 'b', 'c'); // ['b', 'c'];```代替 apply。```html:runfunction foo(x, y, z) {}const args = [1,2,3];// 下面兩句效果相同foo.apply(null, args);foo(...args);```對於 Object 而言,用於組合成新的 Object 。(ES2017 stage-2 proposal)```htmlconst foo = { a: 1, b: 2,};const bar = { b: 3, c: 2,};const d = 4;const ret = { ...foo, ...bar, d }; // { a:1, b:3, c:2, d:4 }```此外,在 JSX 中 Spread Operator 還可用於擴展 props,詳見 Spread Attributes。PromisesPromise 用於更優雅地處理異步請求。好比發起異步請求:```htmlfetch('/api/todos') .then(res => res.json()) .then(data => ({ data })) .catch(err => ({ err }));```定義 Promise 。```htmlconst delay = (timeout) => { return new Promise(resolve => { setTimeout(resolve, timeout); });};delay(1000).then(_ => { console.log('executed');});```Generatorsdva 的 effects 是經過 generator 組織的。Generator 返回的是迭代器,經過 yield 關鍵字實現暫停功能。這是一個典型的 dva effect,經過 yield 把異步邏輯經過同步的方式組織起來。```html:runapp.model({ namespace: 'todos', effects: { *addRemote({ payload: todo }, { put, call }) { yield call(addTodo, todo); yield put({ type: 'add', payload: todo }); }, },});```#### 2、React Component一、Stateless Functional ComponentsReact Component 有 3 種定義方式,分別是 React.createClass, class 和 Stateless Functional Component。推薦儘可能使用最後一種,保持簡潔和無狀態。這是函數,不是 Object,沒有 this 做用域,是 pure function。好比定義 App Component 。```htmlfunction App(props) { function handleClick() { props.dispatch({ type: 'app/create' }); } return <div onClick={handleClick}>${props.name}</div>}```等同於:```htmlclass App extends React.Component { handleClick() { this.props.dispatch({ type: 'app/create' }); } render() { return <div onClick={this.handleClick.bind(this)}>${this.props.name}</div> }}```二、JSXComponent 嵌套相似 HTML,JSX 裏能夠給組件添加子組件。```html<App> <Header /> <MainContent /> <Footer /></App>```classNameclass 是保留詞,因此添加樣式時,需用 className 代替 class 。<h1 className="fancy">Hello dva</h1>JavaScript 表達式JavaScript 表達式須要用 {} 括起來,會執行並返回結果。好比:```html<h1>{ this.props.title }</h1>```Mapping Arrays to JSX能夠把數組映射爲 JSX 元素列表。```html<ul> { this.props.todos.map((todo, i) => <li key={i}>{todo}</li>) }</ul>```註釋儘可能別用 // 作單行註釋。```html<h1> {} {} { // single line } Hello</h1>```Spread Attributes這是 JSX 從 ECMAScript6 借鑑過來的頗有用的特性,用於擴充組件 props 。好比:```htmlconst attrs = { href: 'http://example.org', target: '_blank',};<a {...attrs}>Hello</a>```等同於```htmlconst attrs = { href: 'http://example.org', target: '_blank',};<a href={attrs.href} target={attrs.target}>Hello</a>```Props數據處理在 React 中是很是重要的概念之一,分別能夠經過 props, state 和 context 來處理數據。而在 dva 應用裏,你只需關心 props 。propTypesJavaScript 是弱類型語言,因此請儘可能聲明 propTypes 對 props 進行校驗,以減小沒必要要的問題。```htmlfunction App(props) { return <div>{props.name}</div>;}App.propTypes = { name: React.PropTypes.string.isRequired,};```內置的 prop type 有:```htmlPropTypes.arrayPropTypes.boolPropTypes.funcPropTypes.numberPropTypes.objectPropTypes.string```往下傳數據(示意圖略)往上傳數據(示意圖略)三、CSS Modules定義全局 CSSCSS Modules 默認是局部做用域的,想要聲明一個全局規則,可用 :global 語法。好比:```html.title { color: red;}:global(.title) { color: green;}```而後在引用的時候:```html<App className={styles.title} /> // red<App className="title" /> // green```classnames Package在一些複雜的場景中,一個元素可能對應多個 className,而每一個 className 又基於一些條件來決定是否出現。這時,classnames 這個庫就很是有用。```htmlimport classnames from 'classnames';const App = (props) => { const cls = classnames({ btn: true, btnLarge: props.type === 'submit', btnSmall: props.type === 'edit', }); return <div className={ cls } />;}```這樣,傳入不一樣的 type 給 App 組件,就會返回不一樣的 className 組合:```html<App type="submit" /> // btn btnLarge<App type="edit" /> // btn btnSmall```#### 3、Reducerreducer 是一個函數,接受 state 和 action,返回老的或新的 state 。即:(state, action) => state增刪改以 todos 爲例。```htmlapp.model({ namespace: 'todos', state: [], reducers: { add(state, { payload: todo }) { return state.concat(todo); }, remove(state, { payload: id }) { return state.filter(todo => todo.id !== id); }, update(state, { payload: updatedTodo }) { return state.map(todo => { if (todo.id === updatedTodo.id) { return { ...todo, ...updatedTodo }; } else { return todo; } }); }, },};```嵌套數據的增刪改建議最多一層嵌套,以保持 state 的扁平化,深層嵌套會讓 reducer 很難寫和難以維護。```htmlapp.model({ namespace: 'app', state: { todos: [], loading: false, }, reducers: { add(state, { payload: todo }) { const todos = state.todos.concat(todo); return { ...state, todos }; }, },});```下面是深層嵌套的例子,應儘可能避免。```htmlapp.model({ namespace: 'app', state: { a: { b: { todos: [], loading: false, }, }, }, reducers: { add(state, { payload: todo }) { const todos = state.a.b.todos.concat(todo); const b = { ...state.a.b, todos }; const a = { ...state.a, b }; return { ...state, a }; }, },});```#### 4、Effect示例:```htmlapp.model({ namespace: 'todos', effects: { *addRemote({ payload: todo }, { put, call }) { yield call(addTodo, todo); yield put({ type: 'add', payload: todo }); }, },});```一、Effectsput用於觸發 action 。```htmlyield put({ type: 'todos/add', payload: 'Learn Dva' });```call用於調用異步邏輯,支持 promise 。```htmlconst result = yield call(fetch, '/todos');```select用於從 state 裏獲取數據。```htmlconst todos = yield select(state => state.todos);```二、錯誤處理全局錯誤處理dva 裏,effects 和 subscriptions 的拋錯所有會走 onError hook,因此能夠在 onError 裏統一處理錯誤。```htmlconst app = dva({ onError(e, dispatch) { console.log(e.message); },});```而後 effects 裏的拋錯和 reject 的 promise 就都會被捕獲到了。本地錯誤處理若是須要對某些 effects 的錯誤進行特殊處理,須要在 effect 內部加 try catch 。```htmlapp.model({ effects: { *addRemote() { try { // Your Code Here } catch(e) { console.log(e.message); } }, },});```三、異步請求異步請求基於 whatwg-fetch,API 詳見:https://github.com/github/fetchGET 和 POST```htmlimport request from '../util/request';// GETrequest('/api/todos');// POSTrequest('/api/todos', { method: 'POST', body: JSON.stringify({ a: 1 }),});```統一錯誤處理假如約定後臺返回如下格式時,作統一的錯誤處理。```html{ status: 'error', message: '',}```編輯 utils/request.js,加入如下中間件:```htmlfunction parseErrorMessage({ data }) { const { status, message } = data; if (status === 'error') { throw new Error(message); } return { data };}```而後,這類錯誤就會走到 onError hook 裏。#### 5、Subscriptionsubscriptions 是訂閱,用於訂閱一個數據源,而後根據須要 dispatch 相應的 action。數據源能夠是當前的時間、服務器的 websocket 鏈接、keyboard 輸入、geolocation 變化、history 路由變化等等。格式爲 ({ dispatch, history }) => unsubscribe 。異步數據初始化好比:當用戶進入 /users 頁面時,觸發 action users/fetch 加載用戶數據。```htmlapp.model({ subscriptions: { setup({ dispatch, history }) { history.listen(({ pathname }) => { if (pathname === '/users') { dispatch({ type: 'users/fetch', }); } }); }, },});```path-to-regexp Package若是 url 規則比較複雜,好比 /users/:userId/search,那麼匹配和 userId 的獲取都會比較麻煩。這是推薦用 path-to-regexp 簡化這部分邏輯。```htmlimport pathToRegexp from 'path-to-regexp';// in subscriptionconst match = pathToRegexp('/users/:userId/search').exec(pathname);if (match) { const userId = match[1]; // dispatch action with userId}```#### 6、Router```htmlConfig with JSX Element (router.js)<Route path="/" component={App}> <Route path="accounts" component={Accounts}/> <Route path="statements" component={Statements}/></Route>```詳見:react-router一、Route ComponentsRoute Components 是指 ./src/routes/ 目錄下的文件,他們是 ./src/router.js 裏匹配的 Component。經過 connect 綁定數據好比:```htmlimport { connect } from 'dva';function App() {}function mapStateToProps(state, ownProps) { return { users: state.users, };}export default connect(mapStateToProps)(App);```而後在 App 裏就有了 dispatch 和 users 兩個屬性。Injected Props (e.g. location)Route Component 會有額外的 props 用以獲取路由信息。locationparamschildren更多詳見:react-router二、基於 action 進行頁面跳轉```htmlimport { routerRedux } from 'dva/router';// Inside Effectsyield put(routerRedux.push('/logout'));// Outside Effectsdispatch(routerRedux.push('/logout'));// With queryrouterRedux.push({ pathname: '/logout', query: { page: 2, },});```除 push(location) 外還有更多方法,詳見 react-router-redux#### 7、dva 配置Redux Middleware好比要添加 redux-logger 中間件:```htmlimport createLogger from 'redux-logger';const app = dva({ onAction: createLogger(),});```注:onAction 支持數組,可同時傳入多箇中間件。history切換 history 爲 browserHistory```htmlimport { browserHistory } from 'dva/router';const app = dva({ history: browserHistory,});```去除 hashHistory 下的 _k 查詢參數```htmlimport { useRouterHistory } from 'dva/router';import { createHashHistory } from 'history';const app = dva({ history: useRouterHistory(createHashHistory)({ queryKey: false }),});```