DvaJS

快速上手快速入门案例代码

案例初探

安装 dva

1
2
3
// 安装 dva-cli
npm install dva-cli -g
dva -v

创建项目

1
2
3
4
// 创建项目
dva new dva-quickstart
cd dva-quickstart
npm start

一个警告

index.js:2177 Warning: Please use require(“history”).createHashHistory instead of require(“history/createHashHistory”). Support for the latter will be removed in the next major release.

其实不用管,纠结的话可以修改node_moduels/dva/lib/index.js

var _createHashHistory = _interopRequireDefault(require(“history/createHashHistory”));

1
var _createHashHistory = _interopRequireDefault(require("history").createHashHistory);

使用 AntD

1
npm install antd babel-plugin-import
1
2
3
4
5
6
// 编辑 .webpackrc,使 babel-plugin-import 插件生效
{
"extraBabelPlugins": [
["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
]
}

定义 Products 组件

src/routes/Products.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
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Table, Popconfirm, Button } from "antd";

class Products extends Component {
columns = [
{
title: "Name",
dataIndex: "name",
},
{
title: "Actions",
render: (text, record) => {
return (
<Popconfirm
title="Delete?"
onConfirm={() => {}}
>
<Button>Delete</Button>
</Popconfirm>
);
},
},
];
render() {
return <Table dataSource={this.props.products} columns={this.columns} />;
}
}

Products.propTypes = {
products: PropTypes.array.isRequired,
};

export default Products;

配置路由

src/router.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from "react";
import { Router, Route, Switch } from "dva/router";
import Products from "./routes/Products";

function RouterConfig({ history }) {
return (
<Router history={history}>
<Switch>
<Route path="/products" exact component={Products} />
</Switch>
</Router>
);
}

export default RouterConfig;

定义 Model

src/models/products.js

1
2
3
4
5
6
7
8
9
10
11
12
export default {
namespace: 'products',
state: [
{ key: 1, name: '手机', id: 1 },
{ key: 2, name: '电脑', id: 2 },
],
reducers: {
'delete'(state, { payload: id }) {
return state.filter(item => item.id !== id);
}
}
}

别忘了在 src/index.js 中载入 model

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

connect

src/routes/Products.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
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Table, Popconfirm, Button } from "antd";
import { connect } from "dva";

class Products extends Component {
columns = [
{
title: "Name",
dataIndex: "name",
},
{
title: "Actions",
render: (text, record) => {
return (
<Popconfirm
title="Delete?"
onConfirm={() => this.handleDelete(record.id)}
>
<Button>Delete</Button>
</Popconfirm>
);
},
},
];
// 注意这里加了删除的功能
handleDelete = id => {
this.props.dispatch({
type: "products/delete",
payload: id,
});
};
render() {
return (
<Table dataSource={this.props.products} columns={this.columns} />
);
}
}

Products.propTypes = {
products: PropTypes.array.isRequired,
};

// const mapStateToProps = state => ({ products: state.products });
export default connect(({ products }) => ({ products }))(Products);

使用 BrowserHistory

src/index.js

1
2
import { createBrowserHistory as createHistory } from 'history';
const app = dva({ history: createHistory() });

路由跳转

标签导航

1
2
import { Link } from 'dva/router';
<Link to="/">Home</Link>

编程式导航

1
2
// 如果不是直接挂在到 Route 下面的组件,需要用到高阶组件 withRouter
<Button type="primary" onClick={() => this.props.history.push('/')}>Home</Button>
1
2
import { routerRedux } from 'dva/router';
<Button type="primary" onClick={() => this.props.dispatch(routerRedux.push('/'))}>Home</Button>

数据模拟

/mock/products.js

1
2
3
4
5
6
7
8
9
10
11
const products = [
{ key: 1, name: '手机', id: 1 },
{ key: 2, name: '电脑', id: 2 }
];

// 以函数的形式进行,可以做一些更加灵活的处理(也可以直接写一个对象)
module.exports = {
"GET /api/products": (req, res) => {
res.send(products);
},
}

/mock/users.js

1
2
3
4
5
6
module.exports = {
"GET /api/users": [
{ name: 'ifer' },
{ name: 'elser' },
]
}

/.roadhogrc.mock.js

1
2
3
4
export default {
...require("./mock/products"),
...require("./mock/users")
};
1
2
3
4
5
6
7
8
9
10
11
// 优化
import fs from "fs";
import path from "path";

const mock = {};
fs.readdirSync(path.join(__dirname, "/mock")).forEach(file => {
if (file.match(/\.js$/)) {
Object.assign(mock, require("./mock/" + file));
}
});
export default mock;

/src/services/products.js

1
2
3
import request from "../utils/request";

export const getProducts = () => request("/api/products");

/src/services/users.js

1
2
3
import request from "../utils/request";

export const getUsers = () => request("/api/users");

/src/services/index.js

1
2
export * as users from './users';
export * as products from './products';

/src/routes/Products.jsx

1
2
3
4
5
6
7
import { users, products } from '../services';
class Products extends Component {
componentDidMount() {
users.getUsers().then(console.log);
products.getProducts().then(console.log);
}
}

数据交互

实现数据的获取、删除

获取数据

src/models/products.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { products } from '../services';

export default {
namespace: 'products',
state: [],
reducers: {
'init'(state, { payload }) {
return [...payload];
}
},
effects: {
*getProducts(action,{ call, put }) {
const { data } = yield call(products.getProducts);
yield put({ type: 'init', payload: data } );
}
}
}

src/routes/Products.jsx

1
2
3
4
5
class Products extends Component {
componentDidMount() {
this.props.dispatch({ type: 'products/getProducts' });
}
}

删除数据

src/routes/Products.jsx

1
2
3
4
5
6
handleDelete = id => {
this.props.dispatch({
type: "products/deleteProduct",
payload: id,
});
};

src/models/products.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { products } from '../services';

export default {
namespace: 'products',
state: [],
reducers: {
'init'(state, { payload }) {
return [...payload];
}
},
effects: {
*deleteProduct({ payload }, { call, put }) {
const { data } = yield call(products.deleteProduct, payload);
yield put({ type: 'init', payload: data } );
}
}
}

src/services/products.js

1
export const deleteProduct = id => request(`/api/deleteProduct?id=${id}`);

mock/products.js

1
2
3
4
5
6
7
8
// 以函数的形式进行,可以做一些更加灵活的处理(也可以直接写一个对象)
module.exports = {
"GET /api/deleteProduct": (req, res) => {
const idx = products.findIndex(item => item.id === req.query.id);
products.splice(idx, 1);
res.send(products);
},
}

优化引入 models

src/models/index.js

1
2
3
const context = require.context('./', false, /\.js$/);

export default context.keys().filter(item => item !== './index.js').map(key => context(key));

src/index.js

1
require('./models').default.forEach(key => app.model(key.default));

redux-actions

1
yarn add redux-actions

src/actions/index.js

1
2
3
4
import { createAction } from 'redux-actions';

export const getProducts = createAction('products/getProducts');
export const deleteProduct = createAction('products/deleteProduct');

src/routes/Products.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { deleteProduct, getProducts } from '../actions';
class Products extends Component {
handleDelete = id => {
// this.props.dispatch({
// type: "products/deleteProduct",
// payload: id,
// });
this.props.dispatch(deleteProduct(id));
};
componentDidMount() {
// this.props.dispatch({ type: 'products/getProducts' });
this.props.dispatch(getProducts());
}
}