文章目录
- 前言
- 表单处理实践
- Modal 清空旧数据
- 使用Form.create和getFieldDecorator对Form进行包装
- 表单控件是switch
- 自定义表单控件
- Table列表
- Select
- showSearch 用于在选择框中显示搜索框
- 其他需要注意的组件
- Tabs
- 其它
前言
目前,很多中小企业前端实现采用了react+antd技术栈。在传统业务中,大部分后台的表单和列表模式类似,通过antd组件快速搭建,可以节约时间成本。本文是实际场景中的一些实践记录,供大家参考。
表单处理实践
Modal 清空旧数据
组件有标准的 React 生命周期,关闭后状态不会自动清空。 如果希望每次打开都是新内容,需要自行手动清空旧的状态。或者打开时给 Modal 设置一个全新的 key, React 会渲染出一个全新的对话框。
第一种清空方式:显示弹窗时设置一个新的key
componentWillReceiveProps(nextprops){
//打开时设置一个新key
if(nextprops.visible)
this.setState({newKey:!this.state.newKey})
}
<Modal key={this.state.newKey} visible={this.props.visible} />
第二种清空方式:关闭弹窗后,清空弹窗数据,弹窗内是表单时重置表单。
afterClose = () => {
// 隐藏动画完成后重置内容
this.props.form.resetFields();
}
<Modal afterClose={this.afterClose} visible={this.props.visible} />
使用Form.create和getFieldDecorator对Form进行包装
经过包装的Form具备以下特点:
使用 Form.create 处理后的表单具有自动收集数据并校验的功能;
不再需要用 onChange 来做同步,但还是可以继续监听 onChange 等事件;
不能用控件的 value或defaultValue 等属性来设置表单域的值,默认值可以用 getFieldDecorator 里的 initialValue;
不需要用 setState,可以使用 this.props.form.setFieldsValue 来动态改变表单值,使用 this.props.form.getFieldsValue来获取表单值。
包装一个表单包括三个步骤:
第一步:
class CustomizedForm extends React.Component {}
CustomizedForm = Form.create({})(CustomizedForm);
第二步:使用getFieldDecorator装饰对应的field
const FormItem = Form.Item;
const formItemLayout = {
labelCol: { span: 5 },
wrapperCol: { span: 12 }
};
<Form onSubmit={this.submit}>
<FormItem {...formItemLayout} label={'名称'}>
{getFieldDecorator('fieldName', {
//rules是表单的校验规则
rules: [{ required: true, message: '请填写表单字段!' }],
initialValue:'初始值'
})(
<Input />
)}
</FormItem>
</Form>
第三步:submit时添加校验逻辑和后处理
submit = (e)=> {
//这里去掉默认行为,可以避免用户操作回车时提交表单
e.preventDefault();
//校验表单的值
this.props.form.validateFields((err, values) => {
if (!err) {
//后处理操作
}
}
}
表单控件是switch
switch开关的值参数名是checked而不是value,使用时有两种方式:
第一种:引入团队封装后的switch控件
tnpm install @ali/uniform-react-components --save
import Switch from ‘@ali/uniform-react-components/lib/Switch/index’;
第二种: 使用valuePropName
<FormItem {…formItemLayout} label=“Switch” > {getFieldDecorator(‘switch’, { valuePropName: ‘checked’ })(
自定义表单控件
自定义表单控件的需求场景很多,为了能够支持 antd Form 的 getFieldDecorator 包装,从而使组件能直接用于 Form 中,需要遵守以下规则:
提供受控属性 value 或其它与 valuePropName 的值同名的属性。
提供 onChange 事件或 trigger 的值同名的事件。
不能是函数式组件。
大CMS业务中用到的自定义表单控件有很多,例如UploadImg,TreeSelect,BizTypeSelect等等,具体可以参考youku-manager和youku-scg等工程里面的实践。
Table列表
第一步:定义columns 即列表中列的描述
columns = [{
title: 'ID',
dataIndex: 'id',
key: 'id'
}, {
title: '内容数量',
key: 'amount',
//render用于生成复杂数据的渲染函数,参数分别为当前行的值,当前行数据
render: (text,record) => <a onClick={()=>this.relationTagPopup(record)}>查看</a>,
},{
title: '操作',
key: 'action',
//删除,下线等post操作使用PopConfirm进行二次确认
render: (item) => (
<div>
<a onClick={() => this.editTag(item)}>编辑</a>
<span className='ant-divider'></span>
<Popconfirm title="确定删除吗?" onConfirm={()=>this.deleteTag(item)}>
<a>删除</a>
</Popconfirm>
</div>
)
}];
第二步:定义Table
rowKey属性是表格行 key 的取值,这里取的是column中的id字段,保证key值不重复
loading表示页面是否加载中,在翻页和查询时添加load效果,可以优化体验
dataSource 数据数组
<Table columns={this.columns} loading={state.loading} dataSource={state.tagList} pagination={{ pageSize:10, total: state.total, current: state.pageNo, }} onChange={this.handlePagination} rowKey="id" />
第三步:更新dataSource数据源
this.setState({loading:true})
IO.getTagRepertoryList(data)
.then(response => {
if(response.success){
const data = response.data;
const tagList = data.itemList;
const total = data.totalSize;
const pageNo = data.pageNo;
const loading = false;
this.setState({tagList,pageNo,total,loading});
}
else{
Message.warning(response.message)
this.setState({
tagList:[],
total:0,
pageNo:1,
loading:false
});
}
})
.catch(err => {
console.error(err);
this.setState({
loading:false,
tagList:[],
total:0,
pageNo:1,
});
Message.error('查询异常,无结果');
});
Select
cms后台的业务有很多选择选择下拉需求,下面两个业务常见的select为例,解释几个比较重要的配置
多值远程搜索
render() {
//fetching 查询状态,用于在接口数据返回前显示loading效果
const { fetching, personList } = this.state;
// Option的name属性对应的是optionLabelProp设置的值
const options = personList.map((d,index)=>
<Option key={index} value={(d.id).toString()}
name={d.name}>
<div className='person-item-img'><img src={d.thumbUrl} />
</div>
<span className='person-item'>{d.name}</span>
<span className='person-item'>{d.birthday}</span>
</Option>)
return ( <div className='person-search'>
<Select defaultValue={this.props.defaultVal}
mode="multiple" placeholder="输入相关人物名称检索选择"
notFoundContent={fetching ? <Spin size="small" /> : null}
optionLabelProp='name' filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
onFocus={this.fetchUser} style={{ width: '100%' }}
labelInValue >
{options}
</Select>
</div>
)
optionLabelProp和labelInValue配合,默认情况下 onChange 里只能拿到 value,如果需要拿到选中的节点文本 label,可以使用 labelInValue 属性,这个人物搜索组件的Option的children是ReactNode,所以我们需要通过optionLabelProp拿到需要的文本,回显在input框中,同时给后端传递所需要的map结构。
filterOption是否根据输入项进行筛选,如果这个select的option是通过用户输入关键字请求接口渲染的,那么需要设置成false,否则可以通过特定函数设定内部筛选规则,具体的使用方法在第二个例子中具体说明。
单值内部搜索
<Select className='select-feature'
showSearch
filterOption={(input, option) => {
return (input === option.props.value || option.props.name.indexOf(input)>-1)
}}
value={props.id+''}
onChange={this.handleChange}
>
{this.featureOptions}
</Select>
showSearch 用于在选择框中显示搜索框
filterOption 在这里定义内部搜索逻辑,默认是通过option的value进行匹配,这里为了能既能够通过id也能够通过name进行搜索,通过这个函数进行了特殊设置。
使用Fetch进行接口调用
import {fetchGet, fetchPost, fetchJsonp} from '@ali/uniform-react-components/lib/UniFetch/index';
//大cms后台的post请求需要增加token校验
const _tb_token_ = window._tb_token_;
const params = {
//fetch 请求默认是不带 cookie 的
credentials: 'include',
//是否允许跨域
//mode: "no-cors",
headers: {
//服务端通过识别X-Requested-With来判定发送的fetch请求是ajax请求,数据统一封装成Json返回
"X-Requested-With":"XMLHttpRequest"
},
}
class IO {
/** * get 请求例子 */
static fetchGetSample(data) {
return fetchGet('/video/v5video/item/search.htm?', data,params);
}
/** * post 请求例子 */
static fetchPostSample(data) {
data._tb_token_ = _tb_token_;
return fetchPost('/video/v5video/item/add.htm', data,params);
}
/** * jsonp 请求例子 */
static fetchJsonpSample(tags) {
return fetchJsonp('/common/jsonp/getTagName.htm?', {tags: tags},params);
}
}
const data = {key1:value1,key2:value2}
IO.fetchPostSample({data})
.then(response=>{})
.catch(err=>{})
其他需要注意的组件
Tabs
注意如果两个tab有数据耦合,需要在切换时有针对性的刷新,比如将标签页面和标签维度放一个tabs中,如果在标签维度中添加了数据,那么切换回标签页面时,标签维度的select选项必须同步,最直接的做法就是刷新页面。
Button htmlType='submit’最常用
InputNumber 利用formatter和parser可以限制InputNumber的输入格式,比如限制用户只能输入一位小数
Spin 模拟loading状态
其它
使用diamond进行前后端分离和环境区分
目前采用diamond进行页面级别的前后端分离,工作流为:
- 后端提供接口和预埋数据,后台发布与前端发布隔离,这里的预埋数据前端可以通过window.paramName的全局方式获取;
- 后端提供日常,预发和线上三个环境的diamond地址,diamond中加入 用于前端react渲染;
- 前端git发版本,手动在三个diamond环境内添加css和js资源,更新版本号;
- 由于不同的子后台域名不同,使用其他子后台接口时需要利用diamond区分接口环境,例如在优酷选品后台中要使用cms后台节目搜索接口,
在日常的diamond中设置
window.PROGRAM_SEARCH_URL = ‘//haibao.alibaba.net/youku/api/filter/show.json’;
在预发和线上则改成
window.PROGRAM_SEARCH_URL = ‘//haibao.alibaba.com/youku/api/filter/show.json’;前端通过window.PROGRAM_SEARCH_URL获得真实的接口地址。 - 存在的问题:diamond方案虽然可以使版本升级通过推送提高效率,但会产生前后端无法同时生效、回滚异常等副作用,需要进一步思考。