多译 · 更新日志页面开发(四) 在实践中使用Umi+Dva+Ant Design

之前色老师给我们线上开过一个小会,会议内容是带领我们浏览一下多译的代码,以及umi框架的架构。

这几天我完成了自己MiniProject的前端部分,在这里记录一下这一套框架的简单使用情况。

1、路由的配置

在config目录下,有一个router.ts,其中定义了路由信息。

我照猫画虎,在已有的页面后面增加了changelog界面。

关于这部分,我其实并不能理解其路由的结构,尤其是其嵌套的形式。TODO: 以后有时间好好读一下umi的文档。(最近确实比较忙,还是先把项目做出来再说吧)

2、源代码的结构

源代码的目录下主要由几部分构成

1
2
3
4
5
components   ------一些组件
layouts
models ------model的定义,或者说Redux的定义
pages ------页面
services ------提供给model的一些方法,诸如http请求等

说来惭愧,layouts部分色老师也讲过,但是我好巧不巧当时走神了。后来看了一下也没太能看懂…..

TODO: 抽空了解一下 layouts的作用

3、model 结构

model由一个 connect.d.ts 和 若干个ts文件组成。其中前者像是model的入口文件,其他ts文件则分别定义了若干个store

这一点在之前学习Redux的时候,印象中阮一峰老师有提到过大的项目中store通常会分开管理,然后Redux提供了将它们合并的方法。

我在model中新建了 changelog.ts 文件,用来定义更新日志的store,代码如下

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import { DvaModel, Effect } from '@/models/connect';
import { getChangelog, uploadChangelog } from '@/services/changelog';

export interface LogItem {
url: {
win: string;
mac: string;
};
version: string;
createdAt: Date;
updatedAt: Date;
description: string;
}

export interface ChangelogModelState {
data: LogItem[];
IsSuccess: boolean;
messege: String;
}

export interface ChangelogModel extends DvaModel<ChangelogModelState> {}

const Model: ChangelogModel = {
state: {
data: [],
IsSuccess: false,
messege: '',
},
reducers: {
save(state, { payload }) {
return {
...state,
...payload,
};
},
},
effects: {
*fetchData(_, { call, put }) {
const res = yield call(getChangelog);
const { success, data } = res;
if (success) {
yield put({
type: 'save',
payload: { data },
});
}
},
*uploadData({ payload }, { call, put }) {
let data = payload;
try {
const res = yield call(uploadChangelog, data);
if (res && res.success) {
yield put({
type: 'save',
payload: { IsSuccess: true },
});
}
} catch (e) {
const { response } = e;
const { status } = response;
if (status === 409) {
yield put({
type: 'save',
payload: { messege: '版本已存在' },
});
}
if (status === 401) {
yield put({
type: 'save',
payload: { messege: '没有权限提交' },
});
}
}
},
},
};
export default Model;

首先需要定义store中state的接口,如代码中的 ChangelogModelState,这里的命名我参照了色老师其他store的命名,以此来规范代码中的变量名。

接着定义了model的接口,ChangelogModel,这个接口继承了 DvaModel,并且将自己定义的state的类型作为泛型实例化。

最后利用继承来的类型来定义Model,并且完成state的初始化。

reducers 和 effects 共同定义了action的类型,其中effects定义了具有副作用的action。包括那些需要进行网络请求的action。 reduces则是一些直接更新state的纯函数。

在effects中,我们可以传递call put 方法,其中call方法可以用来调用request,put则可以通过action来执行reducer中的方法。

一个值得注意的点: 在reducer和effects中参数中的payload要用解构赋值, fetchData({payload},_) 这样payload才是我们调用dispatch时传入的payload。(最开始没有用解构赋值,然后发现获取不到数据,查bug查了好久,哭)

接着在connect.d.ts中添加changelog的入口。(这部分代码由于涉及色老师写的,因此不方便贴出来)

TODO: 抽空去看一下 .d.ts 这个后缀的文件名和 .ts 之间的区别

4、页面(组件)

4.1 组件结构

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
@connect(({ changelog }: ConnectState) => ({
data: changelog.data,
}))
export default class Changelog extends Component<DispatchProps> {
componentDidMount(): void {
this.props.dispatch({ type: 'changelog/fetchData' });
}
render() {
return (
<Fragment>
<Row>
<Col span="1.5"></Col>
<Col span="22.5">
<h1>更新日志</h1>
</Col>
</Row>
<Timeline>{this.myTimelineItem()}</Timeline>
</Fragment>
);
}
myTimelineItem() {
return this.props.data.map((item, index) => {
return (
<Timeline.Item>
<Row>
<Col span="1"></Col>
<Col span="23">
<Typography>
<Title level={3}>{item.version}</Title>
{this.myDate(item.createdAt)}
<Paragraph>
<ul>{this.myLi(item.description)}</ul>
</Paragraph>
</Typography>
</Col>
</Row>
</Timeline.Item>
);
});
}
myLi(description: String) {
let arr = description.split('\n');
let sta = [];
//去除空字符串
for (let i = 0; i < arr.length; i++) if (arr[i] === '') sta.push(i);
for (let i = 0; i < sta.length; i++) arr.splice(sta[i], 1);
return arr.map((item, index) => <li>{item}</li>);
}
myDate(createdAt) {
if (createdAt) {
let arr = createdAt.split('T');
return <Tag>{arr[0]}</Tag>;
}
}
}

首先要先 connect,这里是Dva的语法

接着就是组件类的定义。didMount 生命周期中发送 action: fetchData,这是个异步的action,会发送网络请求,然后更新state。根据上面我们model中的定义,会在请求结束后拿到数据后更新changelog model中的data数据。同时View也会相应的进行响应。

render函数中是对于界面的描述。

4.2 Ant Design

色老师给我推荐了一个组件库 Ant Design。 我去官网找了个时间轴组件,按照组件介绍的代码直接使用,发现其使用方法非常简单。

使用data数组的 map 方法来返回 Timeline的item。 这一点参考了之前的React实践视频

5、感悟

在别人项目的基础上写代码真是一件过于easy的事情(不是。

正经:在别人项目的基础上进行实践来学习新知识真是一件很爽的事情。我们只需要关注自己需要的部分,想办法解决手头很明确的需求,在此过程中,或者查文档,或者问学长,很快解决之后能够立刻学到很多。在这次Miniproject的过程中,我从对于React一无所知,到尝试使用了 Umi、Dva等等等框架。尽管我对于那些框架其实并没有很了解,但是我相信在之后的实践过程中总有一天会把这些TODO给逐渐消除。