一、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