react antd實現(xiàn)動態(tài)增減表單
之前寫動態(tài)表單遇到過坑,就是用index下標做key會導致bug,而且很嚴重!
今天有空寫下文章記錄下:怎么處理和邏輯
我用的是antd3的版本,3和4的表單有點不一樣,不過差別應該不大。
需求:
1、選擇類型切換展示固定的模板
2、通過新增字段可以動態(tài)增減表單里面的每一行
3、控制每一行的字段是否需要必填
4、編輯時候回填參數(shù)
效果圖:



部分關鍵代碼:
import React, { Component } from 'react';
import styles from './index.less';
import {
Table,
Button,
Select,
Popconfirm,
Modal,
Form,
Input,
Radio,
Row,
Col, Tooltip,
Icon,
message,
Pagination, InputNumber,
} from 'antd';
const Option = Select.Option;
const FormItem = Form.Item;
let id = 0;
@Form.create()
class Index extends Component {
marketId = 0;
state = {
selectType: '',
orderType: 1, //文章1 地圖2
typeLoading: false,
isEdit: false,
lookVisible: false,
visible: false,
pageSize: 10,
pageNum: 1,
keyWord: '',
row: {},
typeList: {},
mock: {},
mapType: [{
'fieldName': 'name',
'isImg': 0,
'order': 0,
'remarks': '名稱',
}, {
'fieldName': 'label',
'isImg': 0,
'order': 0,
'remarks': '標簽',
}, {
'fieldName': 'lon',
'isImg': 0,
'order': 0,
'remarks': '經(jīng)度',
}, {
'fieldName': 'lat',
'isImg': 0,
'order': 0,
'remarks': '緯度',
}],
articleType: [{
'fieldName': 'name',
'isImg': 0,
'order': 0,
'remarks': '名稱',
}, {
'fieldName': 'label',
'isImg': 0,
'order': 0,
'remarks': '標簽',
}],
};
/**
* 將動表單態(tài)值生成需要的數(shù)據(jù)格式
* @param values
* @returns {[]}
*/
createValues = (values) => {
const { row } = this.state;
const data = [];
const newValues = { // 用新的對象承載提交的數(shù)據(jù)
...values,
};
const fieldNameData = []; // 保存fieldName值
const remarksData = []; // 保存remarks值
const isImgData = []; // 保存isImg值
const orderData = []; // 保存orderData值
const fieldName = RegExp(/fieldName/);
const remarks = RegExp(/remarks/);
const isImg = RegExp(/isImg/);
for (const key in newValues) {
if (fieldName.test(key)) {
fieldNameData.push(newValues[key]);
}
}
for (const key in newValues) {
if (remarks.test(key)) {
remarksData.push(newValues[key]);
}
}
for (const key in newValues) {
if (isImg.test(key)) {
isImgData.push(newValues[key]);
}
}
for (const key in newValues) {
if (isImg.test(key)) {
orderData.push(newValues[key]);
}
}
fieldNameData.forEach((item, index) => {
data.push({
fieldName: item,
remarks: remarksData[index],
isImg: isImgData[index],
order: orderData[index],
id: row.dataType ? row.dataType.id : '',
});
});
return data;
};
handleOk = e => {
this.props.form.validateFields((err, values) => {
if (!err) {
const { row, isEdit } = this.state;
const params = {
dataType: {
name: values.name,
type: values.type,
id: row.dataType ? row.dataType.id : '',
},
typeFields: [],
};
params.typeFields = this.createValues(values);
if (isEdit) {
editType(params).then(res => {
if (res.code === 0) {
message.info('修改成功');
this.setState({
visible: false,
isEdit: false,
});
this.fetchTypeList();
this.props.form.resetFields();
}
});
} else {
addType(params).then(res => {
if (res.code === 0) {
message.info('新增成功');
this.setState({
visible: false,
isEdit: false,
});
this.fetchTypeList();
this.props.form.resetFields();
}
});
}
}
});
};
lookOrEditTypeModal = (flag, record) => {
const { articleType, mapType } = this.state;
if (flag === 'add') { //添加默認為文章模板
this.marketId = articleType.length + 1; //設置動態(tài)key標記長度
this.setState({
visible: true,
row: { typeFields: articleType },
});
} else if (flag === 'edit') {
this.setState({
visible: true,
});
getType({ dataTypeId: record.id }).then(res => {
if (res.code === 0) {
this.marketId = res.data.typeFields.length + 1; //設置動態(tài)key標記長度
this.setState({
row: res.data,
isEdit: flag === 'edit',
});
}
});
} else {
this.setState({
lookVisible: true,
});
getType({ dataTypeId: record.id }).then(res => {
if (res.code === 0) {
this.setState({
row: res.data,
});
}
});
}
};
onChangeType = (value) => {
const { form } = this.props;
const { orderType, row, articleType, mapType } = this.state;
this.props.form.resetFields();
const params = {};
if (value === 1) { //文章類型
params['typeFields'] = articleType;
this.marketId = articleType.length + 1;
} else {
params['typeFields'] = mapType;
this.marketId = mapType.length + 1;
}
this.setState({
row: params,
orderType: value,
});
};
//刪除方法?。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。?!
removeFile = k => {
const { form } = this.props;
const keys = form.getFieldValue('keys');
if (keys.length === 1) {
return;
}
form.setFieldsValue({
keys: keys.filter(key => key !== k),
});
};
//添加方法?。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。?!
addFile = () => {
const { form } = this.props;
const keys = form.getFieldValue('keys');
const nextKeys = keys.concat(this.marketId++);
form.setFieldsValue({
keys: nextKeys,
});
};
judgeIsTemplet = (data) => {
if (!data) {
return false;
}
if ((data.fieldName === 'lat') || (data.fieldName === 'lon') || (data.fieldName === 'label') || (data.fieldName === 'name')) {
return true;
}
};
handleValidator = (rule, val, callback) => {
if (!val) {
callback();
}
let validateResult = /^[5A-Za-z0-9-\_]+$/.test(val);
if (!validateResult) {
callback('請輸入正確表字段');
}
callback();
};
columns = [
{
title: '類型名稱',
dataIndex: 'name',
key: 'name',
width: 500,
},
{
title: '所屬類型',
dataIndex: 'type',
key: 'type',
render: (text) => {
return text === 1 ? '文章' : '地圖';
},
},
{
title: '操作',
dataIndex: 'address',
key: 'address',
render: (text, record) => {
return <div>
<Button type='link' onClick={() => this.lookOrEditTypeModal('look', record)}>查看</Button>
<Button type='link' onClick={() => this.lookOrEditTypeModal('edit', record)}>編輯</Button>
<Popconfirm title="確認刪除?" onConfirm={() => this.deleteTypeClick(record)}>
<Button type='link'>刪除</Button>
</Popconfirm>
</div>;
},
},
];
render() {
const { selectType, typeLoading, mock, row, isEdit, typeList, keyWord, lookVisible } = this.state;
const { getFieldDecorator, getFieldValue } = this.props.form;
let typeFields = row.typeFields || [];
const initData = [];
typeFields.forEach((item, index) => {//根據(jù)真實數(shù)據(jù),設置默認keys數(shù)組
initData.push(index);
});
getFieldDecorator('keys', { initialValue: initData }); //給表單增加keys字段,并設置默認值,這里編輯時候可以生成編輯回填的效果。
const keys = getFieldValue('keys');
const formItems = keys.map((k) => (
<Row gutter={12} key={k} className={styles.form_row}>
<FormItem label="字段" key={`fieldName_${k}`}>
{getFieldDecorator(`fieldName_${k}`, {
initialValue: row.typeFields[k] ? row.typeFields[k].fieldName : '',
validateTrigger: ['onChange', 'onBlur'], //校驗子節(jié)點值的時機
rules: [{
required: true,
message: '請輸入英文字段!',
}, {
validator: this.handleValidator,
}],
})(<Input placeholder="請輸入英文字段" max={30} disabled={this.judgeIsTemplet(row.typeFields[k])}/>)}
</FormItem>
<FormItem label="名稱" key={`remarks_${k}`}>
{getFieldDecorator(`remarks_${k}`, {
initialValue: row.typeFields[k] ? row.typeFields[k].remarks : '',
validateTrigger: ['onChange', 'onBlur'],
rules: [{
required: true,
message: '請輸入中文名稱!',
}],
})(<Input placeholder="請輸入中文名稱" disabled={this.judgeIsTemplet(row.typeFields[k])}/>)}
</FormItem>
<FormItem label="排序" key={`order_${k}`}>
{getFieldDecorator(`order_${k}`, {
initialValue: row.typeFields[k] ? row.typeFields[k].order : 0,
})(<InputNumber style={{width:75}} placeholder="排序" />)}
</FormItem>
<FormItem label="圖片" key={k}>
{getFieldDecorator(`isImg_${k}`, {
initialValue: row.typeFields[k] ? row.typeFields[k].isImg : 0,
rules: [{
required: true,
}],
})(<Radio.Group disabled={this.judgeIsTemplet(row.typeFields[k])}>
<Radio value={0}>否</Radio>
<Radio value={1}>是</Radio>
</Radio.Group>)}
</FormItem>
{!this.judgeIsTemplet(row.typeFields[k]) ? (
<Icon type="minus-circle" onClick={() => this.removeFile(k)} title='刪除'/>
) : null}
</Row>
));
return (
<div className={styles.wrap_type}>
<Modal
title="類型管理"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
width={890}
// className={styles.modal_type}
maskClosable={false}
>
<Form layout='inline'>
<Row style={{ textAlign: 'center', marginBottom: 14 }}>
<FormItem label="選擇類型">
{getFieldDecorator('type', {
initialValue: row.dataType ? row.dataType.type : 1,
rules: [{
required: true,
}],
})(<Select onChange={this.onChangeType} disabled={isEdit} style={{ width: 200 }}>
<Option value={1}>文章類型</Option>
<Option value={2}>地圖類型</Option>
<Option value={3} disabled={true}>文件類型</Option>
</Select>)}
</FormItem>
<FormItem label="類型名稱">
{getFieldDecorator('name', {
initialValue: row.dataType ? row.dataType.name : '',
rules: [{
required: true,
message: '請輸入類型名稱!',
}],
})(<Input placeholder="請輸入類型名稱" style={{ width: 200 }}/>)}
</FormItem>
</Row>
{formItems}
<div style={{ margin: 'auto', textAlign: 'center' }}>
<Button icon="plus" onClick={this.addFile} style={{ marginTop: 10 }}>新增字段</Button>
</div>
</Form>
</Modal>
</div>
);
}
}
export default Index;
關鍵地方是設置一個marketID作為動態(tài)添加的key,然后用他的值作為動態(tài)key。(千萬不要用數(shù)組的下標index來作為key)!
到此這篇關于react antd實現(xiàn)動態(tài)增減表單的文章就介紹到這了,更多相關react antd動態(tài)增減表單內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
詳解create-react-app 自定義 eslint 配置
這篇文章主要介紹了詳解create-react-app 自定義 eslint 配置,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06
基于React Native 0.52實現(xiàn)輪播圖效果
這篇文章主要為大家詳細介紹了基于React Native 0.52實現(xiàn)輪播圖效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-11-11
React中useCallback useMemo使用方法快速精通
在React函數(shù)組件中,當組件中的props發(fā)生變化時,默認情況下整個組件都會重新渲染。換句話說,如果組件中的任何值更新,整個組件將重新渲染,包括沒有更改values/props的函數(shù)/組件。在react中,我們可以通過memo,useMemo以及useCallback來防止子組件的rerender2023-02-02
記錄React使用connect后,ref.current為null問題及解決
記錄React使用connect后,ref.current為null問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05

