dva是一个轻量级的数据流解决方案,基于redux和redux-saga,还内置react-router和fetch。

dav简化了项目的数据流的构建方式,项目构建流程分为:快速构建项目->编写路由文件->注册路由->编写UI组件文件->定义Model->载入Model->模型和组件connect起来。

一、项目构建

1.快速构建项目

使用工具快速构建项目模板。

1
2
3
4
5
6
7
$ npm install dva-cli -g

$ dva new dva-quickstart

$ cd dva-quickstart

$ npm start

项目即在本地8000端口启动。

2.编写路由文件

先安装ant和按需加载antd的扩展babel-plugin-import

$ npm install antd babel-plugin-import --save

新建路由组件routes/Products.js

1
2
3
4
5
6
7
import React from 'react';

const Products = (props) => (
<h2>List of products</h2>
);

exprot default Products;
3.注册路由

编辑router.js

1
2
3
import Products from './routes/Products';

<Route path="/Products" exact component={Products} />
4.编写UI组件文件

新建UI组件components/ProductList.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
import React from 'react';
import PropTypes from 'prop-types';
import { Table, Popconfirm, Button } from 'antd';

const ProductList = ({ onDelete, products }) => {
const columns = [{
title: '名称',
dataIndex: 'name',
}, {
title: '操作',
render: (text, record) => {
return (
<Popconfirm title="Delete?" onConfirm={() => onDelete(record.id)}>
<Button>Delete</Button>
</Popconfirm>
);
},
}];
return (
<Table
dataSource={products}
columns={columns}
/>
);
};

ProductList.propTypes = {
onDelete: PropTypes.func.isRequired,
products: PropTypes.array.isRequired,
};

export default ProductList;
5.定义Model模型

dva通过model的概念把一个领域的模型管理起来,其中包括同步更新state的reducers,处理异步逻辑的effects,数据源订阅的subscriptions。

新建models/products.js

1
2
3
4
5
6
7
8
9
export default {
namespace: 'products',
state: [],
reducers: {
'delete'(state, { payload: id }) {
return state.filter(item => item.id !== id);
},
},
};

namespace 表示在全局 state上的 key
state 是初始值,在这里是空数组
reducers 等同于redux里的 reducer,接收 action,同步更新 state

6.载入模块

在index.js里面载入模块

app.model(require('./models/products').default);

7.model连接component

dva提供connect方法。编辑routes/Products.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from 'react';
import { connect } from 'dva';
import ProductList from '../components/ProductList';

const Products = ({ dispatch, products }) => {
function handleDelete(id) {
dispatch({
type: 'products/delete',
payload: id,
});
}
return (
<div>
<h2>List of Products</h2>
<ProductList onDelete={handleDelete} products={products} />
</div>
);
};

// export default Products;
export default connect(({ products }) => ({
products,
}))(Products);

最后在index.js里面初始化一些数据,一个简单的dva应用完成。

1
2
3
4
5
6
7
8
const app = dva({
initialState: {
products: [
{ name: 'dva', id: 1 },
{ name: 'antd', id: 2 },
],
},
});
8.打包发布应用

npm run build

二、dva数据流

数据改变的发生通常是通过用户的交互行为或者浏览器行为(如路由跳转)触发的,当此类行为会改变数据的时候会通过dispatch发起一个Action,如果是同步行为直接通过Reducers改变State,如果是异步行为会先触发Effects流向Reducers最后改变State。

数据流向

2.1.State对象

State通常是一个JavaScript对象,表示某个模型全局的状态数据,可以在模型里面配置相关信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default{
namespace: 'modelname',
state: {
//...
},
subscriptions: {
//...
},
effects: {
//...
},
reducer: {
//...
},
}
2.2.Action对象

Action是一个普通JavaScript对象,是改变state的一个行为,也是唯一改变state的途径,想要改变state,需要将Action传入dispatch函数中,该函数是在组件connect模型后通过props传入的。

1
2
3
this.props.dispatch({type: 'modelname/xxx'}).then((v={}) => {
console.log(v)
})
2.3.dispatch方法

dispatch函数连接路由组件和模型,通过传入Action调用model中的逻辑改变state,Action只是描述了一个行为,dispatch是触发这个行为,而reducers则是如何改变这个行为。

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
// component
this.props.dispatch({type: 'modelname/getInfo'}).then((v={}) => {
console.log(v)
})
// model
export default{
namespace: 'modelname',
state: {
getdate: {},
},
subscriptions: {
//...
},
effects: {
*getInfo({payload}, {call, put, select}) {
const data = yield call(getInfo)

if (!data) return

yield put({
type: 'change',
payload: {
name: 'getdate',
value: data
}
})

return data
},
},
reducer: {
change(state, action) {
const {payload} = action

if (!payload || !payload.name) return

return {
...state,
[payload.name]: payload.value
}
}
},
}
2.4.connect方法

connect函数路由使组件和模型联系起来,可以在connect函数里面传入mapStateToProps函数,用于把model中的state和组件的props一一映射,mapStateToProps函数接收state作为参数,返回一个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyComponent extends component{
// ...
}

const mapStateToProps = state => {
return {
data1: state.modelname.data1,
data2: state.modelname.data2,
// ...
}
}

export default connect(mapStateToProps)(MyComponent);
2.5.Reducers

reducers可以同步直接更改state,该设计来源于高阶函数reduce,接收两个参数:之前已经累积运算的结果和当前要被累积的值,返回的是一个新的累积结果。
示例详见2.3小结,通过put方法调用reducers的change函数改变state。

2.6.Effects

effects可以调用异步操作,获取的异步数据流向reducers然后改变state,由于采用了generator的相关概念,所以将异步转成同步写法,从而将effects转为纯函数。示例详见2.3小结,函数*getInfo就是一个generator函数的形式,
payload是dispatch调用action时传入的参数对象,第二个参数为方法的集合,分别是call、put、select。

call以异步的方式调用异步函数,第二个参数对象可选

const data = yield call(asyncFn, ...args)

put用于触发action

yield put({ type: 'change', payload: {name: 'getdate', value: data}})

select用于从state中获取数据

const list = yield select(state => state.list)

2.7.subscriptions

subscriptions可以监听数据源,可以简单理解为一个监听器,可以监听路由变化,鼠标,键盘变化,服务器连接变化,状态变化等,这样在其中就可以根据不同的变化做出相应的处理。例如监听路由和click事件:

1
2
3
4
5
6
7
8
9
10
11
12
subscriptions: {
onClick ({dispatch}) {
document.addEventListener('click',() => {
dispatch (type:"handleClick")
})
},
setupHistory({dispatch,history}){
history.listen((location) => {
console.log(location)
})
},
}