一、Reducer
reducer 是一個函數,接受 state 和 action,返回老的或新的 state 。即:(state, action) => state
增刪改
以 todos 為例。
app.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 很難寫和難以維護。
app.model({ namespace: 'app', state: { todos: [], loading: false, }, reducers: { add(state, { payload: todo }) { const todos = state.todos.concat(todo); return { ...state, todos }; }, }, });
下面是深層嵌套的例子,應盡量避免。
app.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 }; }, }, });
二、Effect
示例:
app.model({ namespace: 'todos', effects: { *addRemote({ payload: todo }, { put, call }) { yield call(addTodo, todo); yield put({ type: 'add', payload: todo }); }, }, });
Effects
put
用於觸發 action 。
yield put({ type: 'todos/add', payload: 'Learn Dva' });
call
用於調用異步邏輯,支持 promise 。
const result = yield call(fetch, '/todos');
select
用於從 state 里獲取數據。
const todos = yield select(state => state.todos);
錯誤處理
全局錯誤處理
dva 里,effects 和 subscriptions 的拋錯全部會走 onError hook,所以可以在 onError 里統一處理錯誤。
const app = dva({ onError(e, dispatch) { console.log(e.message); }, });
然后 effects 里的拋錯和 reject 的 promise 就都會被捕獲到了。
本地錯誤處理
如果需要對某些 effects 的錯誤進行特殊處理,需要在 effect 內部加 try catch 。
app.model({ effects: { *addRemote() { try { // Your Code Here } catch(e) { console.log(e.message); } }, }, });
異步請求
異步請求基於 whatwg-fetch,API 詳見:https://github.com/github/fetch
GET 和 POST
import request from '../util/request'; // GET request('/api/todos'); // POST request('/api/todos', { method: 'POST', body: JSON.stringify({ a: 1 }), });
統一錯誤處理
假如約定后台返回以下格式時,做統一的錯誤處理。
{
status: 'error', message: '', }
編輯 utils/request.js,加入以下中間件:
function parseErrorMessage({ data }) { const { status, message } = data; if (status === 'error') { throw new Error(message); } return { data }; }
然后,這類錯誤就會走到 onError hook 里。
三、Subscription
subscriptions 是訂閱,用於訂閱一個數據源,然后根據需要 dispatch 相應的 action。數據源可以是當前的時間、服務器的 websocket 連接、keyboard 輸入、geolocation 變化、history 路由變化等等。格式為 ({ dispatch, history }) => unsubscribe 。
異步數據初始化
比如:當用戶進入 /users 頁面時,觸發 action users/fetch 加載用戶數據。
app.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 簡化這部分邏輯。
import pathToRegexp from 'path-to-regexp'; // in subscription const match = pathToRegexp('/users/:userId/search').exec(pathname); if (match) { const userId = match[1]; // dispatch action with userId }
四、router
Config with JSX Element (router.js)
<Route path="/" component={App}> <Route path="accounts" component={Accounts}/> <Route path="statements" component={Statements}/> </Route>
詳見:react-router
Route Components
Route Components 是指 ./src/routes/ 目錄下的文件,他們是 ./src/router.js 里匹配的 Component。
通過 connect 綁定數據
比如:
import { 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 用以獲取路由信息。
- location
- params
- children
更多詳見:react-router
基於 action 進行頁面跳轉
import { routerRedux } from 'dva/router'; // Inside Effects yield put(routerRedux.push('/logout')); // Outside Effects dispatch(routerRedux.push('/logout')); // With query routerRedux.push({ pathname: '/logout', query: { page: 2, }, });
除 push(location) 外還有更多方法,詳見 react-router-redux
五、dva配置
Redux Middleware
比如要添加 redux-logger 中間件:
import createLogger from 'redux-logger'; const app = dva({ onAction: createLogger(), });
注:onAction 支持數組,可同時傳入多個中間件。
history
切換 history 為 browserHistory
import { browserHistory } from 'dva/router'; const app = dva({ history: browserHistory, });
去除 hashHistory 下的 _k 查詢參數
import { useRouterHistory } from 'dva/router'; import { createHashHistory } from 'history'; const app = dva({ history: useRouterHistory(createHashHistory)({ queryKey: false }), });
六、工具
通過 dva-cli 創建項目
先安裝 dva-cli 。
$ npm install dva-cli -g
然后創建項目。
$ dva new myapp
最后,進入目錄並啟動。
$ cd myapp
$ npm start
