背景
最近这段时间都不是很忙,于是就抽空研究了一下dva,以前接触过react,做过一个问卷的小项目,代码在这里,在对react有一点了解的情况下,决定开始直接进攻dva,先去看了一些相关文档和介绍,对此有一个简略的笔记,现在我准备开始做一个小项目,目标就是实现基础的CRUD吧。
安装和初始化项目
安装
1 | $ (sudo) npm install dva-cli -g |
创建应用
新目录中进行初始化1
2$ dva new project
$ cd project
已有目录中进行初始化1
2
3$ mkdir project
$ cd project
$ dva init
配置antd
1 | $ npm install antd --save |
babel-plugin-import 用户按需引入antd的js和css文件。
同时使用antd,dva时,通常需要配置额外的babel plugin,修改.roadhogrc
,在extraBabelPlugins
里加上:1
["import", {"libraryName": "antd", "style": "css"}]
自动生成手脚架代码
1 | $ dva g route router_name // 生成路由 |
项目构建
文件目录
修改后的文件目录
布局
新建routes/MainContainer.js1
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
30import React, {Component, PropTypes} from 'react';
import {Layout} from 'antd';
import { connect } from 'dva';
const {Header, Content, Footer, Sider} = Layout;
function MainContainer({children, location, dispatch}) {
return (
<Layout>
<Sider style={{overflow: 'auto', background: '#fff'}}>
</Sider>
<Layout>
<Header style={{background: '#fff', padding: 0}} />
<Content>
{children}
</Content>
<Footer style={{textAlign: 'center'}}>
copyrigth ©2017 Created by Mogu
</Footer>
</Layout>
</Layout>
);
}
MainContainer.propTypes = {
location: PropTypes.object,
dispatch: PropTypes.func
};
// 建立数据关联关系
export default connect()(MainContainer);
新建IndexPage,并引入MainContainer.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26import React from 'react';
import { connect } from 'dva';
import styles from './IndexPage.css';
import MainContainer from '../MainContainer';
function IndexPage({location}) {
return (
<MainContainer location={location}>
<div className={styles.normal}>
<h1 className={styles.title}>Yay! Welcome to dva!</h1>
<div className={styles.welcome} />
<ul className={styles.list}>
<li>To get started, edit <code>src/index.js</code> and save to reload.</li>
<li><a href="https://github.com/dvajs/dva-docs/blob/master/v1/en-us/getting-started.md">Getting Started</a></li>
</ul>
</div>
</MainContainer>
);
}
IndexPage.propTypes = {
};
export default connect()(IndexPage);
菜单栏
新建components/Menus/Menus.jsx1
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
37import React, {Component, PropTypes} from 'react';
import {Menu, Icon} from 'antd';
import {Link} from 'dva/router';
const SubMenu = Menu.SubMenu;
function Menus({}) {
const menuProps = {};
return (
<Menu mode="inline" {...menuProps} selectedKeys={[location.pathname]}>
<Menu.Item key="/">
<Icon type="home" />Home
</Menu.Item>
<SubMenu key="1" title={<span><Icon type="team" />User</span>}>
<Menu.Item key="/users">
UserList
</Menu.Item>
<Menu.Item key="/a">
A菜单
</Menu.Item>
</SubMenu>
<SubMenu key="2" title={<span><Icon type="setting" />Other</span>}>
<Menu.Item key="/b">
B菜单
</Menu.Item>
<Menu.Item key="/c">
C菜单
</Menu.Item>
</SubMenu>
</Menu>
);
}
Menus.propTypes = {
}
export default Menus;
修改MainContainer.js,引入Menu.jsx1
2
3
4
5
6
7
8
9
10......
import Menus from '../components/Menus/Menus.jsx';
function MainContainer({children, location, dispatch}) {
return (
<Layout>
<Sider style={{overflow: 'auto', background: '#fff'}}>
<Menus {...menuProps}/>
</Sider>
......
路由配置
大致意思就是
1.默认的IndexPage页面显示默认的内容。
2.点击左侧菜单栏,右侧内容会局部刷新。
利用MainContainer.js中的children
属性替换右侧显示内容
修改route.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
25import React from 'react';
import { Router, Route } from 'dva/router';
import IndexPage from './routes/IndexPage';
import Users from "./routes/Users";
import A from './routes/A";
import B from './routes/B";
import C from './routes/C";
import D from './routes/D";
function RouterConfig({ history }) {
return (
<Router history={history}>
<Route path="/" component={IndexPage} />
<Route path="/users" component={Users} />
<Route path="/a" component={A} />
<Route path="/b" component={B} />
<Route path="/c" component={C} />
<Route path="*" component={D} />
</Router>
);
}
export default RouterConfig;
修改Menus.jsx,给左侧菜单加点击切换路由效果,也就是Link标签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 ......
return (
<Menu mode="inline" {...menuProps} selectedKeys={[location.pathname]}>
<Menu.Item key="/">
<Link to="/"><Icon type="home" />Home</Link>
</Menu.Item>
<SubMenu key="1" title={<span><Icon type="team" />User</span>}>
<Menu.Item key="/users">
<Link to="/users">UserList</Link>
</Menu.Item>
<Menu.Item key="/a">
<Link to="/a">A菜单</Link>
</Menu.Item>
</SubMenu>
<SubMenu key="2" title={<span><Icon type="setting" />Other</span>}>
<Menu.Item key="/b">
<Link to="/b">B菜单</Link>
</Menu.Item>
<Menu.Item key="/c">
<Link to="/c">C菜单</Link>
</Menu.Item>
</SubMenu>
</Menu>
);
}
......
至此,大致的页面显示应该都有了,但是涉及到动态变化的操作都没有实现。
首先我们新建一个model文件,执行dva g model nav
,它会在models下建立一个nav.js,并注入到index.js。1
app.model(require('./models/nav);
修改models/nav.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15export default {
namespace: 'nav',
state: {
navOpenKeys: JSON.parse(localStorage.getItem(`navOpenKeys`)) || []
},
subscriptions: {
},
effexts: {},
reducers: {
handleNavOpenKeys(state, {payload: navOpenKeys}) {
return {...state, ...navOpenKeys};
}
}
}
这是菜单栏的主要数据逻辑,然后将它和菜单栏关联起来。
修改MainContainer.js1
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......
function MainContainer({children, location, dispatch, app}) {
const {navOpenKeys} = app.nav;
const menuProps = {
location,
navOpenKeys,
changeOpenKeys(openKeys) {
localStorage.setItem(`navOpenKeys`, JSON.stringify(openKeys));
dispatch({type: 'nav/handleNavOpenKeys', payload: {navOpenKeys: openKeys}})
}
}
return (
<Layout>
<Sider style={{overflow: 'auto', background: '#fff'}}>
<Menus {...menuProps}/>
</Sider>
<Layout>
<Header style={{background: '#fff', padding: 0}} />
<Content>
{children}
</Content>
<Footer style={{textAlign: 'center'}}>
copyrigth ©2017 Created by Mogu
</Footer>
</Layout>
</Layout>
);
}
MainContainer.propTypes = {
location: PropTypes.object,
dispatch: PropTypes.func,
app: PropTypes.object
};
// 指定订阅数据,关联了users
function mapStateToProps(app) {
return {app};
}
// 建立数据关联关系
export default connect(mapStateToProps)(MainContainer);
修改Menus.jsx1
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......
function Menus({location, navOpenKeys, changeOpenKeys}) {
const levelMap = {};
const menuProps = {
openKeys: navOpenKeys,
onOpenChange
}
// 保持选中
function getAncestorKeys(key) {
const map = [];
const getParent = (index)=>{
const result = [String(levelMap[index])];
if (levelMap[result[0]]) {
result.unshift(getParent(result[0])[0]);
}
return result;
}
for(let index in levelMap) {
if ({}.hasOwnProperty.call(levelMap, index)) {
map[index] = getParent(index);
}
}
return map[key] || [];
}
function onOpenChange(openKeys) {
const latestOpenKey = openKeys.find(key => !(navOpenKeys.indexOf(key)>-1));
const latestCloseKey = navOpenKeys.find(key => !(openKeys.indexOf(key)>-1));
let nextOpenKeys = [];
if (latestOpenKey) {
nextOpenKeys = getAncestorKeys(latestOpenKey).concat(latestOpenKey);
}
if (latestCloseKey) {
nextOpenKeys = getAncestorKeys(latestCloseKey)
}
changeOpenKeys(nextOpenKeys);
}
return (
<Menu mode="inline" {...menuProps} selectedKeys={[location.pathname]}>
......
Menus.propTypes = {
navOpenKeys: PropTypes.array,
changeOpenKeys: PropTypes.func
}
export default Menus;
至此,我们的关联就做好了,菜单栏会根据用户的点击事件动态改变。
面包屑
新建components/breads/breads.jsx1
2
3
4
5
6
7
8
9
10
11import React from 'react';
import {Link} from 'react-router';
import {Breadcrumb} from 'antd';
function Breads() {
return (
<Breadcrumb separator=">" />
);
}
export default Breads;
修改MainContainer.js,并引入breads.js1
2
3
4
5import Breads from '../components/breads/breads.jsx';
.......
<Header style={{background: '#fff', padding: 0}} >
<Breads />
</Header>