基于umi的项目搭建

最近看了蚂蚁金服的可插拔的企业级react应用框架umi。相比自己现有的开发模式具备较多优点,可更好地支持企业级开发。其中较为明显的两点是:

1.基于路由,模块拆分更加直观和规范。
2.基于redux的数据流方案(dva),满足大型应用对数据流控制的要求。model层解耦,更利于业务逻辑编写和维护。

初步写了一个示例项目project-framework,以下以此项目为例做一些说明。此外官方还有一系列 示例项目 可以参考,其中 antd-admin 是antd+dva较为完整的示例。

1.基于路由,模块拆分更加直观和规范化

项目基本结构图如下
avatar
主要文件说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
config --配置文件
mock --mock数据文件
src --源文件
layouts --全局布局
models --全局级别的数据
pages --页面文件
goods --商品页
components --商品组件
goods-list.js --商品列表
search-bar.js --搜索框
services --商品页api请求构造
index.js --商品页入口文件
model.js --商品页的数据
index.js --主页
services --整个应用所有的api接口
api.js --api url
global.js --全局数据相关的api请求构造
index.js --api接口入口文件
utils --工具包

可以看到我们的工程模块是基于路由划分的,拆分直观且合理,下面具体介绍其中一些文件夹:

mock –mock数据文件
goods.js –商品页数据mock文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// mock/goods.js
import { apiPrefix } from '../config/config.js'

module.exports = {
[`GET ${apiPrefix}/goods`](req, res) {
const data = {
data: [{
goodName: '商品1'
},{
goodName: '商品2'
},{
goodName: '商品3'
}]
};
if (data) {
res.status(200).json(data)
} else {
}
},
};

src –源文件
layouts –全局布局
index.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
import { Component } from 'react';
import { connect } from 'dva';
import { Layout, Menu, Icon } from 'antd';
import Link from 'umi/link'

const { Header, Sider } = Layout;


@connect(({ global }) => ({ userAuth: global.userAuth }))
class GlobalLayout extends Component {
state = {
collapsed: false,
};

toggle = () => {
this.setState({
collapsed: !this.state.collapsed,
});
}

render() {
const { userAuth } = this.props;

return (
<Layout>
<Sider collapsed={this.state.collapsed}>
<Menu mode="inline" defaultSelectedKeys={['1']}>
<Menu.Item key="1">
<Link to="/">
<Icon type="star" />
<span>首页</span>
</Link>
</Menu.Item>
<Menu.Item key="2">
<Link to="goods">
<Icon type="star" />
<span>商品页</span>
</Link>
</Menu.Item>
</Menu>
</Sider>
<Layout>
<Header style={{ background: '#fff', padding: 0 }}>
<Icon
type={this.state.collapsed ? 'menu-unfold' : 'menu-fold'}
onClick={this.toggle}
/>
<span>权限校验:你的权限是:{userAuth.auth}</span>
</Header>
{ this.props.children }
</Layout>
</Layout>
);
}
}

export default GlobalLayout;

src –源文件
layouts –全局布局
models –全局级别的数据
global.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
import api from '../services'

const { queryUserAuth } = api

export default {
namespace: 'global',
state: {
userAuth: {}
},
reducers: {
setUserAuth(state, { payload }) {
return { ...state, userAuth: payload };
},
},
subscriptions: {
setup({ dispatch }) {
dispatch({ type: 'query' })
},
},
effects: {
*query({ payload }, { call, put, select }) {
const { success, data } = yield call(queryUserAuth, payload)
if (success) {
yield put({ type: 'setUserAuth', payload: data})
}
}
}
}

src –源文件
pages –页面文件
goods –商品页
model.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
import api from '../../services'
import { pathMatchRegexp } from '../../utils/path.js'

const { queryGoods } = api

export default {
namespace: 'goods',
state: {
goodsList: [],
searchGood: ''
},
reducers: {
setGoodsList(state, { payload }) {
return { ...state, goodsList: payload };
},
setSearchGood(state, { payload }) {
return { ...state, searchGood: payload };
},
},
subscriptions: {
setup({ dispatch, history }) {
history.listen(location => {
if (pathMatchRegexp('/goods', location.pathname)) {
dispatch({ type: 'query' })
}
})
},
},
effects: {
*query({ payload }, { call, put, select }) {
const { success, data } = yield call(queryGoods, payload)
if (success) {
yield put({ type: 'setGoodsList', payload: data})
}
}
}
}

src –源文件
pages –页面文件
goods –商品页
services –商品页api请求构造
goods.js

1
2
3
4
5
6
7
8
9
10
11
12
import { request } from '../../../utils'
import { config } from '../../../../config/config'

const { apiPrefix } = config

export function query(params) {
return request({
url: `${apiPrefix}/goods`,
method: 'get',
data: {},
})
}

src –源文件
services –整个应用所有的api接口
api.js –api url

1
2
3
4
export default {
queryGoods: '/goods',
queryUserAuth: '/userAuth'
}

2.基于redux的数据流方案(dva),可满足大型应用对数据流控制的要求。model层解耦,更利于编写业务逻辑和维护。

由前面的model文件,比如下面商品页的数据。我们可以看到数据流可以很好进行控制。model是有层级的,上级的数据可以被所有的下级组件拿到,上级组件无法拿到下级和同级组件的数据。比如权限信息可以在全局共享,商品信息只在商品页使用,所以其他页面拿不到这个数据。如果其他页面也需要拿到商品信息数据,那应该将model定义的层级往上提升。这样的规则使得数据层级变得规范,更有利于大型项目数据流的维护。

src –源文件
pages –页面文件
goods –商品页
model.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
import api from '../../services'
import { pathMatchRegexp } from '../../utils/path.js'

const { queryGoods } = api

export default {
namespace: 'goods',
state: {
goodsList: [],
searchGood: ''
},
reducers: {
setGoodsList(state, { payload }) {
return { ...state, goodsList: payload };
},
setSearchGood(state, { payload }) {
return { ...state, searchGood: payload };
},
},
subscriptions: {
setup({ dispatch, history }) {
history.listen(location => {
if (pathMatchRegexp('/goods', location.pathname)) {
dispatch({ type: 'query' })
}
})
},
},
effects: {
*query({ payload }, { call, put, select }) {
const { success, data } = yield call(queryGoods, payload)
if (success) {
yield put({ type: 'setGoodsList', payload: data})
}
}
}
}

0%