1 初识Dva
1.1 定义
dva 首先是一个基于redux
和redux-saga
的数据流方案,然后为了简化开发体验,dva 还额外内置了react-router
和fetch
,所以也可以理解为一个轻量级的应用框架。
1.2 Dva数据流概念
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch
发起一个 action,如果是同步行为会直接通过 Reducers
改变 State
,如果是异步行为(副作用)会先触发 Effects
然后流向 Reducers
最终改变 State
,所以在 dva 中,数据流向非常清晰简明。

1.3 model层重要API
- namespace
model 的命名空间,同时也是他在全局 state 上的属性,只能用字符串,不支持通过 .
的方式创建多层命名空间。
- state
状态初始值
- reducers
以 key/value 格式定义 reducer。用于处理同步操作,唯一可以修改 state
的地方。由 action
触发。格式为 (state, action) => newState
或 [(state, action) => newState, enhancer]
。
- effects
以 key/value 格式定义 effect。用于处理异步操作和业务逻辑,不直接修改 state
。由 action
触发,可以触发 action
,可以和服务器交互,可以获取全局 state
的数据等等。
格式为 *(action, effects) => void
或 [*(action, effects) => void, { type }]
。
- subscriptions
以 key/value 格式定义 subscription。subscription 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。在 app.start()
时被执行,数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。
格式为 ({ dispatch, history }, done) => unlistenFunction
。
- Action
Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。无论是从 UI 事件、网络回调,还是 WebSocket 等数据源所获得的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。action 必须带有 type
属性指明具体的行为,其它字段可以自定义,如果要发起一个 action 需要使用 dispatch
函数;需要注意的是 dispatch
是在组件 connect Models以后,通过 props 传入的。
- dispatch函数
dispatching function 是一个用于触发 action 的函数,action 是改变 State 的唯一途径,但是它只描述了一个行为,而 dipatch 可以看作是触发这个行为的方式,而 Reducer 则是描述如何改变数据的。
在 dva 中,connect Model 的组件通过 props 可以访问到 dispatch,可以调用 Model 中的 Reducer 或者 Effects。
2 使用
2.1 路由跳转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import { routerRedux } from 'dva/router';
// 在effects中使用
yield put(routerRedux.push('/logout'));
// 在effects外使用
dispatch(routerRedux.push('/logout'));
// 带参数跳转
routerRedux.push({
pathname: '/logout',
query: {
page: 2,
},
});
|
3 示例
通过一个简单的表格数据获取与延时删除demo,熟悉dva数据流的概念、结构及主要API
文件目录
userApi.js
1
2
3
4
5
6
7
8
9
10
|
// 返回假数据
export function getAllUsers() {
return {
content: [
{ id: 1, age: 13, name: 'Jack' },
{ id: 2, age: 32, name: 'Sam' },
{ id: 3, age: 45, name: 'Micheal' },
],
};
}
|
Index.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
import React from 'react';
import { connect } from 'dva';
import { Table } from 'antd';
const UserManage = (props) => {
// 把下面mapStateToProps方法return的数据从props中取出
// 经过connect后dispatch也会自动传入props
const { users, dispatch } = props;
// 获取数据的回调
const getUsers = () => {
// 执行dispatch方法,参数为action对象
dispatch({
// action对象必须包括type属性,固定写法“命名空间/方法名”,
// 可以理解为触发该命名空间中某个方法,给方法传递参数可以在后面写key:value形式,没有可以不写
type: 'user/getAllUsers',
});
};
// 删除数据的回调
const deleteUser = (id) => {
dispatch({
type: 'user/deleteUser',
// 为model层中的deleteUser方法传入id
id,
});
};
// 延时删除数据的回调
const delayRemoveUser = (id) => {
dispatch({
type: 'user/delayRemoveUser',
id,
});
};
// 表格列配置
const columns = [
{ title: '序号', dataIndex: 'id' },
{ title: '名称', dataIndex: 'name' },
{
title: '修改',
render: (_, record) => [
// 这里的回调要注意使用箭头函数
<button onClick={() => deleteUser(record.id)}>删除</button>,
<button onClick={() => delayRemoveUser(record.id)}>1s后删除</button>,
],
},
];
return (
<>
<button type="button" onClick={getUsers}>
获取数据
</button>
<Table rowKey="id" dataSource={users} columns={columns} />
</>
);
};
// 将state映射为props
// 参数中的state表示全部model层的state
const mapStateToProps = (state) => {
// 这里只用到user中的state,所以state.user把命名空间为user这个model层的state数据取出来
const { users } = state.user;
// 这里return出去的数据,会变成此组件的props,在组件可以通过props.num取到。
// props变化了,会重新触发render方法,界面也就更新了。
return {
users,
};
};
// 使用connect连接组件和model层数据
// connect方法用来连接models层的state数据,参数常用的有2个,是第一个mapStateToProps,第二个mapDispatchToProps
// mapStateToProps按字面意思:把models层state数据变为组件的props
// mapDispatchToProps:用了此方法,dispatch只会在此方法里。不写该参数,dispatch会作为组件的props。(我平常用几乎不写该方法)
export default connect(mapStateToProps)(UserManage);
// 简写,将user命名空间下的全部state解构出来作为参数,再将它们都返回出来
export default connect(({user})=>({...user}))(UserManage);
|
userModel.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
/* eslint-disable comma-dangle */
import { message } from 'antd';
import { getAllUsers } from '../services/userApi';
// 延时函数,返回promise对象
function delay(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, time);
});
}
export default {
// 命名空间标识符
namespace: 'user',
// 全部state,可以定义一些默认值
state: {
users: [],
},
// 同步处理数据的方法,唯一能够直接改变state的方法,只能用dispatch触发
// 把新的state retrun出去,用到state数据的界面就会更新,官方推荐处理逻辑都放在effects中
reducers: {
// 方法接收2个参数,第一个是旧的state,第二个是action对象,没有可以不写或写_
setUsers(state, { users }) {
// 先把旧的state全部解构出来,然后把新的users覆盖原来的,从而实现state的更新
return { ...state, users };
},
deleteUser(state, { id }) {
const tem = state.users;
const result = tem.filter(i => id !== i.id);
message.success('删除成功!');
return { ...state, users: result };
}
},
// 异步数据处理的方法
// 推荐数据逻辑处理也应该在此处理,处理完再给reducer
effects: {
// 方法接收两个参数,第一个是传过来的action对象(没有可以写 _ ),第二个基本是用其中call, put, select这3个参数(所有的去官网看)
// call: 用来与后台交互
// put: 用来触发reducers中的方法,与dispacth功能一样
// select: 用来选择models层所有命名空间下的state数据,应用于分页查询时获取page和pageSize
// const page = yield select((state)=>state.user.page)
// * yield是es6的Generator函数
*getAllUsers(_, { call, put }) {
const resp = yield call(getAllUsers);
yield put({
type: 'setUsers',// 这里可以省略命名空间
users: resp.content
});
message.success('查询成功');
},
*delayRemoveUser({ id }, { call, put }) {
yield call(delay, 1000);
yield put({
type: 'deleteUser',
id
});
}
},
// 订阅监听,可以监听路由,键盘输入等,常用作进入某页面发个请求获取数据,展示出来
subscriptions: {
setup({
dispatch, history, query, store,
}) {
return history.listen(({ pathname, search }) => {
if (pathname === '/userPage') {
// 监听进入testPage页时,做些操作
// dispatch({ type: "shoppingWZ" })
}
});
},
},
};
|