这一套很多时候会拿来当做项目的后台管理系统,ant-design提供了丰富的组件,到目前为止使用的体感都很不错,在此记录一些搭建,以及使用时的历程,个人理解难免出现偏差,欢迎纠正。
0.初识DVA
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架 相比于create-react-app 构建的项目我认为dva更易于理解和学习。
那么dva里有什么呢?
过去曾写过react-native,理解过一些关于redux的东西,相较而言redux和dva概念很像,都是在View与数据之间增加了一层用于更新数据的Reducer。更新数据只能通过dispatch一个action,更新的数据也会直观的反应在View上。
dva应该是redux的超集,他提供了更方便的语法糖@connect,loading状态,提供了Effect来用于调用请求,发出action,此外还包含了react-router,fetch。可以说是一个完整的react数据流解决方案。
至于更深层次的理解,那就需要dva的源码解析了
1.如何搭建?
搭建框架这东西还远为熟络,很多时候都是照着文档自己尝试的,很幸运的是ant-design的文档写的很好。
项目实战这里对如何搭dva的框架有了较为详细描述,可以酌情参考。
优化:
上述的简单配置还远未达到开发的要求,我们还需要:
1.动态加载models。
2.将HashRouter转化为BrowserRouter,去掉难看的‘#’。
3.线上配置router(使用数据库的数据)用于在应用中配置不同账号的权限。
4.封装请求,统一做错误判断。
5.配置代理,解决开发中的跨域问题,以及BrowserRouter带来的刷新问题。
6.其他......
2.各类组件使用心得
2.1 一切的开始:路由与Menu组件,路由是后台非常核心的一部分。
2.2 使用频率最高:Table组件
ant-design的表格自带有筛选,分页等功能,配合dva使用起来体感还不错。注意,table需要设置key,不然会报红,如果key相同则会出现难以预见的bug,具体可以尝试下就知道了。
此外和别的表格组件类似,他的显示控制可以在colums中做控制,render中同样可以做一些简单的三目判断。具体如下:
const paginationProps = {
showSizeChanger: true,
showQuickJumper: true,
...ChannelDetail.managerLogs.pagination,
};
<Table
loading={loading}
dataSource={ChannelDetail.managerLogs.list}
pagination={paginationProps}
onChange={this.handleChange}
columns={columns}
// className="small-table"
/>
const columns = [
{
title: '时间',
dataIndex: 'createTime',
key: 'createTime',
render: text => {
return moment(text).format('YYYY-MM-DD hh:mm:ss');
},
},
//也可以简写为
{
title: '时间',
dataIndex: 'createTime',
key: 'createTime',
render: text =>(moment(text).format('YYYY-MM-DD hh:mm:ss');)
},
];
复制代码
分页时,需要传入pagination,并再数据中有同名的分页字段,并再change方法中传入paginationProps中的分页字段名作为参数,loading用于loading状态的控制。
此外,antd自带的表格间距(padding)有些大,可以通过width来调整,而我则是写了个类名来统一缩小padding了。
2.3 非常强大的表单组件:Form
自带表单验证,数据提交等功能,虽然react的表单数据不能双向绑定,但有了他后数据就不需要走state了,他自带强大的set功能,并且自带表单验证,可以用正则,注意使用了FormItem后就不需要使用setState,以及value来做数据绑定了,this.props.form.setFieldsValue更适用,且不会报warning...,另外formItemLayout可以通过与antd的栅格插件来控制label与输入框的长度。示例:
<FormItem label="分成比例" {...formItemLayout}>
{this.props.form.getFieldDecorator('guanli', {
rules: [
{ required: true, message: '管理员分成不能为空' },
{ pattern: /^100$|^(\d|[1-9]\d)(\.\d+)*$/, message: '分成比例应在0~100之间' },
],
initialValue: 0,
})(
<Input
placeholder="请输入管理员分成"
onChange={e => {
e.target.value != 0
? this.props.form.setFieldsValue({
guanli: e.target.value,
yuangong: 100 - e.target.value,
})
: this.props.form.setFieldsValue({ guanli: 0, yuangong: 0 });
}}
type="number"
/>
)}
</FormItem>
复制代码
FormItem的表单验证:
subAdd = e => {
const { dispatch } = this.props;
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
values.divideRatio = `${values.guanli}:${values.yuangong}`; //对参数格式做最后的控制
dispatch({
type: 'channel/addChannel',
payload: {
...values,
},
callback: res => {
if (res.code === 0) {
message.success('添加成功!');
this.props.form.resetFields();
this.searchBtn();
this.setState({ channelId: res.data, addModal: false, configModal: true });
} else {
message.error(res.msg);
}
},
});
}
});
};
复制代码
2.4 弹框Modal。antd的弹框自定义程度还是比较高的,其中包括了正常的弹框和确认框,不过弹框的位置应该不是按照居中来的,弹框较小的时候可能存在位置偏上的情况,如果设计要求较高,那就需要通过global来做矫正咯。
弹出框同样可以用来做成组件,配合上面的formItem使用也是可以的。如下,staffOk中两个参数分别为子组件传递的通过表单校验后的输入参数值,和一个重置表单的方法,这样就能在父组件中调用接口,并且重置表单。而addModal则用来控制子组件的显示与隐藏。
<AddStaff
handleOk={this.staffOk}
ChannelList={dataall}
handleCancel={this.staffCancel}
addModal={this.state.staffModal}
channelId={this.state.channelId}
type={1}
/>
staffOk = (e, func) => {
this.setState({ addModal: false });
const { dispatch } = this.props;
dispatch({
type: 'staff/addChannelStaff',
payload: {
...e,
},
callback: res => {
if (res.code === 0) {
message.success('添加成功!');
this.searchBtn();
func(); // 子组件传递的重置表单方法
this.setState({ staffModal: false });
} else {
message.error(res.msg);
}
},
});
};
复制代码
2.5 栅格组件,antd的栅格组件与bootstrap的类似,分为了24分,支持offset,push之类的位置调控,gutter来控制间距,提供响应式布局,此外还包含了flex的布局控制。
2.6 其他...
3.dva带来的方便与繁杂
3.1 dva-loading dva很强大一个功能来自于他的loding是自带的,能通过监听请求来设置loading的状态,而antd的loading组件很齐全,无论是表格的loading还是Spin组件都能很好的适配。但是dva的loading存在一个问题,当ajax请求出现服务器错误时,他是不会将loading状态取消的,而antd自带的处理是当接口报错时会自动跳转去相应的报错页面,而当你返回时,这个loading状态依然还在,并且只能通过重新加载浏览器才能取消,倘若后台接口处理不周,那尽量还是自己写loading的为好,此外,可能存在一些配置修改dva的loading来对接口报错做出反应,但是暂时不知如何处理......
3.2 redux的vm层中往往会在state中定义初始值,在定义的过程中尽量还是讲数据结构中会出现的层级都写进去,这样在首屏加载时就不会报一些空值的错误,也在后续的处理中方便很多。
3.3 异步请求。很多时候首屏的数据往往只需要一张表格,而后续的一些筛选条件,地区,以及选择列表可以后续加载,这时用异步请求就会大大提升用户体验。
4.eslint的爱与恨
4.1 为了代码规范化,项目中引入了eslint,为了适应其规范,同时引入了prettier来格式化代码,用的是windows的系统,可能会出现crlf 和lf 的报错,于是在.eslintrc文件 rules 里面 配置 "linebreak-style": [0 ,"error", "windows"]。
5.那些年我们踩过的坑
5.1 BrowserRouter带来的刷新浏览器报404的问题。单页应用在不做处理时刷新页面会去寻找路径下的资源而不是从index.js的入口进入,就会导致上述的问题。在开发过程中往往通过proxy做代理来解决这个问题。而在服务器上则需要对服务器做一些相应的配置。这里找到一个比较齐全的解决方案:解决方案
5.2 使用cnpm安装依赖会导致build无法使用的问题。
6.一些记录
6.1 为何对组件的样式修改中必须用:global()才能生效?
react引入了CSS Modules它自动为每一个类生成一个哈希值,可以惟一标志这个类,它让我们可以像使用js模块那样,想用哪部分样式,就引入哪部分样式。但是对组件赋予className时这是通过组件封装将传入值赋予这个组件的class,不会有哈希值,所以这时候就需要:global(),:global()代表的全局变量是不会附加哈希值的,这时样式才能对组件的class生效。
CSS Modules