蘑菇小姐会开花

基于dva-cli和antd的项目实践

背景

最近这段时间都不是很忙,于是就抽空研究了一下dva,以前接触过react,做过一个问卷的小项目,代码在这里,在对react有一点了解的情况下,决定开始直接进攻dva,先去看了一些相关文档和介绍,对此有一个简略的笔记,现在我准备开始做一个小项目,目标就是实现基础的CRUD吧。

安装和初始化项目

安装

1
2
$ (sudo) npm install dva-cli -g
$ dva -v // 查看dva版本

创建应用

新目录中进行初始化

1
2
$ dva new project
$ cd project

已有目录中进行初始化

1
2
3
$ mkdir project
$ cd project
$ dva init

配置antd

1
2
$ npm install antd --save
$ npm install babel-plugin-import --save-dev

babel-plugin-import 用户按需引入antd的js和css文件。
同时使用antd,dva时,通常需要配置额外的babel plugin,修改.roadhogrc,在extraBabelPlugins里加上:

1
["import", {"libraryName": "antd", "style": "css"}]

自动生成手脚架代码

1
2
3
4
$ dva g route router_name // 生成路由
$ dva g model model_name // 生成Model
$ dva g component component_name //生成Component
$ dva g component component_name --no-css //生成Component但是不生成css文件

项目构建

文件目录

修改后的文件目录
目录

布局

新建routes/MainContainer.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
import 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.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
import 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.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
import 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.jsx

1
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
25
import 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.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default {
namespace: 'nav',
state: {
navOpenKeys: JSON.parse(localStorage.getItem(`navOpenKeys`)) || []
},
subscriptions: {

},
effexts: {},
reducers: {
handleNavOpenKeys(state, {payload: navOpenKeys}) {
return {...state, ...navOpenKeys};
}
}
}

这是菜单栏的主要数据逻辑,然后将它和菜单栏关联起来。
修改MainContainer.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
......
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.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
......
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.jsx

1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
import {Link} from 'react-router';
import {Breadcrumb} from 'antd';

function Breads() {
return (
<Breadcrumb separator=">" />
);
}

export default Breads;

修改MainContainer.js,并引入breads.js

1
2
3
4
5
import Breads from '../components/breads/breads.jsx';
.......
<Header style={{background: '#fff', padding: 0}} >
<Breads />
</Header>

坚持原创技术分享,您的支持将鼓励我继续创作!