# form
# 手动设置表单值setFieldsValue
props.form.setFieldsValue({ safeUserPhone: option.key });
# You cannot set a form field before rendering a field associated with the value
WARNING
this.props.form.form.setFieldsValue这个方法里面传值的时候只能是form中用到的参数(即是getFieldDecorator方法中的field)没有的field一律不允许多传,否则就会报错
# key值动态变化,如何动态设置key的值
- 第一种方式
import React, { Component } from 'react'
class App extends Component {
constructor(props) {
super(props)
this.state = {
username: '',
age: '',
sex:''
}
}
handleChange(field, e) {
this.setState({
[field]: e.target.value
})
setTimeout(() => {
console.log(this.state)
}, 10)
}
render() {
return (
<div>
<input onChange={this.handleChange.bind(this, 'username')}></input>
<input onChange={this.handleChange.bind(this, 'age')}></input>
<input onChange={this.handleChange.bind(this, 'sex')}></input>
</div>
);
}
}
export default App;
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
- 第二种方式
import React, { Component } from 'react'
class App extends Component {
constructor(props) {
super(props)
this.state = {
username: '',
age: '',
sex:''
}
}
handleChange(field, e) {
let data = {}
data[field] = e.target.value
this.setState(data)
setTimeout(() => {
console.log(this.state)
}, 10)
}
render() {
return (
<div>
<input onChange={this.handleChange.bind(this, 'username')}></input>
<input onChange={this.handleChange.bind(this, 'age')}></input>
<input onChange={this.handleChange.bind(this, 'sex')}></input>
</div>
);
}
}
export default App;
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
# 表单和数据绑定
antd 支持表单双向绑定,开发过程中无需通过 onChange()回调函数去获取组件的值,通过
getFieldDecorator()可以自动完成数据绑定的功能。后面括号中的组件必须自带onChange方法。。。。。。。否则会拿不到值
{
getFieldDecorator("email", {})(<Input />);
}
2
3
第二个参数是 options,不同的配置可以完成更多的任务,例如必填数据验证
{
let opt = { rules: [{ required: true, message: "the field must supply." }] };
getFieldDecorator("email", opt)(<Input />);
}
2
3
4
也可以完成更多业务逻辑数据验证,例如:
{
let opt = {
rules: [{ type: "email", message: "It's invalid email address." }]
};
getFieldDecorator("email", opt)(<Input />);
}
2
3
4
5
6
还可以指定一个初始值:
{
let opt = { initialValue: "hello@mail.com" };
getFieldDecorator("email", opt)(<Input />);
}
2
3
4
注意:通过 initialValue 指定的初始值,只在第一次 render()中起作用。如果我们通过 API 获取了数据之后,表单数据不会发生变化。 这个时候就要用到 mapPropsToFields()来为字段绑定数据。
{
function mapModelToProps(model) {
return {
item: model.requirement.item,
loading: model.loading.effects["requirement/fetch"]
};
}
function mapPropsToFields(props) {
return {
description: Form.createFormField({
value: props.item.description
})
};
}
export default connect(mapModelToProps)(
Form.create({ mapPropsToFields })(Edit)
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这里有两个函数来 map 所需要的数据:
mapModelToProps()将 state 中所需要的数据映射到 props 上。mapPropsToFields()则将 props 中的数据映射到表单字段上,并更新字段的 value 值。
注意使用这个函数必须用 Form.createFormField()封装需要绑定的字段。
# getFieldDecorator
经过 getFieldDecorator 包装的控件,表单控件会自动添加 value(或 valuePropName 指定的其他属性) onChange(或 trigger 指定的其他属性),数据同步将被 Form 接管,这会导致以下结果:
- 你不再需要也不应该用 onChange 来做同步,但还是
可以继续监听 onChange 等事件。 - 你
不能用控件的 value defaultValue 等属性来设置表单域的值,默认值可以用 getFieldDecorator 里的 initialValue。 - 你不应该用 setState,可以使用 this.props.form.setFieldsValue 来动态改变表单值。
# 清空与重置
清空和重置是不同的概念,清空是把内容都清空掉,而重置是恢复 form 表单初始值。
例如:新增功能,清空和重置就是一样的效果,而对于编辑,清空就是把初始值都清空掉,重置就是恢复刚开始的初始值。
//清空
form.setFieldsValue({"fieldName": ""});
//重置
form.resetFields();
2
3
4
5
# 自定义组件值
项目实例:对 antd RangePicker 抽取完独立组件后,form 表单获取不到值
自定义组件被 getFieldsValue 包裹,会获得以下属性:
onChange方法, 子组件调用此方法,可将值传给父组件,从而Form可拿到自定义组件的值 value属性,获得初始值
<Form.Item label="发送时间">
{getFieldDecorator('range-time-picker', {
rules: [{ required: false, message: '请输入开始时间-结束时间' }],
})(
<RangePickerPage />
)}
</Form.Item>
2
3
4
5
6
7
下面是对 antd RangePicker 进行封装,通过组件 RangePicker 本身的 onChange 方法,调用 this.props.onChange(子组件不用传 onChange 方法,自定义组件被 getFieldsValue 包裹,会自动获取 onChage 属性),则通过 form.validateFields 可以获取到值。
/*
* Desc: 对 antd RangePicker 进行封装
*/
import React from "react";
import moment from "moment";
import { DatePicker } from "antd";
const { RangePicker } = DatePicker;
class RangePickerPage extends React.Component {
range = (start, end) => {
const result = [];
for (let i = start; i < end; i += 1) {
result.push(i);
}
return result;
}
disabledDate = (current) => {
// Can not select days before today and today
return current && current < moment().endOf('day');
}
disabledRangeTime = (_, type) => {
if (type === 'start') {
return {
disabledHours: () => this.range(0, 60).splice(4, 20),
disabledMinutes: () => this.range(30, 60),
disabledSeconds: () => [55, 56],
};
}
return {
disabledHours: () => this.range(0, 60).splice(20, 4),
disabledMinutes: () => this.range(0, 31),
disabledSeconds: () => [55, 56],
};
}
onChange = (dates, dateStrings) => {
const { onChange } = this.props; // !!!
onChange(dateStrings);
}
render() {
return (
<RangePicker
allowClear
disabledDate={this.disabledDate}
disabledTime={this.disabledRangeTime}
showTime={{
hideDisabledOptions: true,
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('11:59:59', 'HH:mm:ss')],
}}
format="YYYY-MM-DD HH:mm:ss"
onChange={this.onChange} // !!!
/>
);
}
}
export default RangePickerPage;
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
# 防抖
例如:表单字段,密码和确认密码,改变 Password,如果与 Confirm Password 不一致,也会在 Confirm Password 做提示:
官网示例:注册新用户,主要代码
compareToFirstPassword = (rule, value, callback) => {
const { form } = this.props;
if (value && value !== form.getFieldValue('password')) {
callback('Two passwords that you enter is inconsistent!');
} else {
callback();
}
};
validateToNextPassword = (rule, value, callback) => {
const { form } = this.props;
if (value && this.state.confirmDirty) {
form.validateFields(['confirm'], { force: true });
}
callback();
};
<Form.Item label="Password" hasFeedback>
{getFieldDecorator('password', {
rules: [
{
required: true,
message: 'Please input your password!',
},
{
validator: this.validateToNextPassword,
},
],
})(<Input.Password />)}
</Form.Item>
<Form.Item label="Confirm Password" hasFeedback>
{getFieldDecorator('confirm', {
rules: [
{
required: true,
message: 'Please confirm your password!',
},
{
validator: this.compareToFirstPassword,
},
],
})(<Input.Password onBlur={this.handleConfirmBlur} />)}
</Form.Item>
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
# 直接使用 rc-form 库 createForm,与 antd Form 的 Form.create() 设置样式不同
import React from 'react'
import PropTypes from 'prop-types'
import { Form, Button } from 'antd'
import { createForm } from 'rc-form'
class BalloonContent extends React.Component {
render() {
const { form } = this.props;
const { getFieldDecorator, getFieldError } = form ;
const stdioOutputError = getFieldError('stdioOutput'); // !!!
return (
<div>
<Form
size='medium'
className={Styles.wrapForm}
>
<Form.Item
label="算子输出"
required // !!!
validateState={stdioOutputError ? 'error' : 'success'} // !!!
help={stdioOutputError} // !!!
>
{form.getFieldDecorator('stdioOutput', {
rules: [
{
required: true,
message: '输出不能为空',
},
],
})(<Input />)}
</Form.Item>
</Form>
</div>
)
}
}
export default createForm ()(BalloonContent) // !!!
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
# Form initialValue 值编辑后,表单的值不改变问题
- 方法一
其实,只要编辑成功后,回调调用form.resetFields(),就可以了,如果是使用 modal 框弹出的表单,就可以直接使用destroyOnClose = {true}属性。 - 方法二
如果是 class 类,可以使用钩子。
componentDidUpdate = (prevProps, prevState) => {
if (!prevProps.visible) {
prevProps.form.resetFields();
}
};
2
3
4
5
# Form.create 方式
- 方式一:@注解
@Form.create()
- 方式二:高阶写法
export default (Form.create({})(APP));
# form 表单提交 htmlType,改为 onClick
<Form layout="inline" onSubmit={this.handleSubmit}>
<FormItem
validateStatus={userNameError ? 'error' : ''}
help={userNameError || ''}
>
{getFieldDecorator('userName', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="Username" />
)}
</FormItem>
<FormItem
validateStatus={passwordError ? 'error' : ''}
help={passwordError || ''}
>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} type="password" placeholder="Password" />
)}
</FormItem>
<FormItem>
<Button
type="primary"
htmlType="submit"
disabled={hasErrors(getFieldsError())}
>
Log in
</Button>
</FormItem>
</Form>
// 改变后:
<Form layout="inline" >
<FormItem
validateStatus={userNameError ? 'error' : ''}
help={userNameError || ''}
>
{getFieldDecorator('userName', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="Username" />
)}
</FormItem>
<FormItem
validateStatus={passwordError ? 'error' : ''}
help={passwordError || ''}
>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} type="password" placeholder="Password" />
)}
</FormItem>
<FormItem>
<Button
type="primary"
disabled={hasErrors(getFieldsError())}
onClick={() => this.handleSubmit()}
>
Log in
</Button>
</FormItem>
</Form>
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
# form.validateFields 直接获取表单的值
this.props.form.validateFields((err, fieldsValue) => {
if (err) return;
this.handleSubmit(fieldsValue);
});
2
3
4
# 利用 validator 和正则,验证中文
<FormItem
hasFeedback={!disableFlag}
labelCol={{ span: 6 }}
wrapperCol={{ span: 15 }}
label="账号"
>
{form.getFieldDecorator("userCode", {
initialValue: "",
rules: [
{ required: !disableFlag, validator: this.usercodeValidator },
{ type: "string", max: 30, message: "账号过长" },
{ whitespace: true, message: "内容不能为空" }
]
})(
<Input
placeholder="请输入账号"
disabled={account}
maxLength="30"
autoComplete="false"
/>
)}
</FormItem>;
usercodeValidator = (rule, value, callback) => {
const { userData } = this.props;
if (!value) {
callback("内容不能为空");
return;
}
// !!!中文验证
const reg = /[\u4E00-\u9FA5]{1,4}/; /*定义验证表达式*/
if (reg.test(value)) {
/*进行验证*/
callback("账号不能为中文");
return;
}
if (userData.userCode === value) {
callback();
} else {
let params = {
userCode: value + "", // 查一下有没有这个编码
useState: "10301"
};
SysUserMgService.checkUserCode(params).then(result => {
if (!result || result.code !== "0") {
callback(result.message);
return;
}
if (result.resultObject && result.resultObject.num !== 0) {
callback("该账号已存在");
return;
}
callback();
});
}
};
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
# form 表单,FormItem 的 rules 中新增 validator,实时请求校验
<FormItem labelCol={{ span: 8 }} wrapperCol={{ span: 15 }} label="菜单名称">
{form.getFieldDecorator("menuName", {
rules: [
{ required: true, message: "菜单名称不能为空" },
{ type: "string", max: 30, message: "菜单名称过长" },
{ validator: this.handleCheckName },
{ whitespace: true, message: "请输入非空白内容" }
],
initialValue: this.props.menuSysData.menuName
})(
<Input
// placeholder="请输入菜单名称"
disabled={disableFlag}
/>
)}
</FormItem>;
// 实时校验
handleCheckName = (rule, value, callback) => {
const { checkName, actionType } = this.state;
if (
!this.trim(value) ||
(checkName && actionType === "M" && this.trim(value) === checkName)
) {
callback();
return;
}
let params = {
menuName: value,
state: "00A"
};
MenuSysService.checkMenuName(params).then(result => {
if (!result || !result.resultObject) {
return;
}
let code = result.resultObject.code;
if (code && code > 0) {
callback("系统名称已存在!");
}
// 必须总是返回一个 callback,否则 validateFields 无法响应
callback();
});
};
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
// 实际项目
// 校验是否选择了省份和市区
const validateProvince = (rule, value, callback) => {
if (value && value.length < 2) {
callback(formatMessage({ id: 'items.select.province' }));
}
callback();
};
<Form.Item label={formatMessage({ id: 'items.address' })}>
{getFieldDecorator('areaCodes', {
initialValue: data.areaCodes,
rules: [
{
required: true,
type: 'array',
message: formatMessage({ id: 'items.empty.province' }),
},
{
validator: validateProvince,
},
],
})(
<Cascader
options={options}
changeOnSelect
placeholder={formatMessage({
id: 'items.select.province',
})}
style={{ width: '100%' }}
/>,
)}
</Form.Item>
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
# form 表单中 FormItem 的布局
使用
getFieldDecorator 包裹的输入框或者 Select,必须是在最外层,也就是只有一层,否则,检验会一直不通过,所以,需要重新布局应该在 getFieldDecorator 的外层添加父节点,而不应该在里面。
<FormItem {...formItemLayout} label="所属应用">
<div>
{getFieldDecorator("apiwgAppName", {
rules: [{ required: false, message: "请选择" }],
initialValue: apiwgAppName || ""
})(
<Input
disabled={this.store.data.apiId ? true : false}
className="control-special"
readOnly
style={{ width: "70%" }}
onClick={this.showModal.bind(this, "apiwgApp")}
/>
)}
<Button
className="btn-modal"
type="primary"
onClick={this.showModal.bind(this, "apiwgApp")}
disabled={this.store.data.apiId ? true : false}
>
// 选择所属应用
</Button>
<a
style={{ marginLeft: "8px" }}
onClick={this.openNewAppDlg.bind(this)}
className={`api-add ${this.store.data.apiId ? "disabled" : ""}`}
>
// +新增应用
</a>
</div>
</FormItem>
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
# 必填校验时
默认校验的属性是:"value",但是对于Upload的话,则是需要校验的值存在fileList属性中,则需要做如下处理:
同时删除Upload组件的fileList属性
const handleUpload = e => {
// console.info(e, '-------------')
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
}
{getFieldDecorator(v.id,{
initialValue: JSON.parse(v.answer||'[]') || [],
valuePropName: 'fileList',
getValueFromEvent: handleUpload,
rules: [
{
required: v.required === '1',
message: formatMessage({ id: 'common.required' }),
},
],
},)(
<Upload
name="file"
multiple={false}
action=""
beforeUpload={file => beforeUpload(file, v.id,index)}
onRemove={ file => handleRemove(file,v.id) }
disabled={!state.edit || load[`${index}`]}
>
{
state.edit && (
<Button icon="upload" loading={load[`${index}`]}>
上传
</Button>
)
}
</Upload>
)}
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
上传完成后还需做可链接处理
const beforeUpload = (file, dtlId,index) => {
if (file.size >= 100 * 1024 * 1024) {
message.info('文件大小超过100MB');
return;
}
// load[index] = true;
// setLoad(load);
setLoad(prevState => ({...prevState, [`${index}`]: true}))
props.dispatch({
type: userDispatchType.upload,
payload: {
files: file,
busiType: 'DutyCompany',
fileType: fileType.Other,
dataOrder: new Date().getTime(),
},
})
.then(res => {
setLoad(prevState => ({...prevState, [`${index}`]: false}))
if (res.code === successCode) {
// load[index] = false;
// setLoad(load);
setDetailObj(prevState => {
const {dtlList=[]}=prevState;
const targetObj = dtlList.find(item=>item.id===dtlId);
const targetIndex = dtlList.findIndex(item=>item.id===dtlId);
const op = {
id: res.data?.[0]?.id || '-1',
uid: res.data?.[0]?.id || '-1',
name: file.name,
url: res.data?.[0]?.url || '',
};
const answer=JSON.parse(targetObj.answer)||[];
if (answer.every(item => item.id !== op.id)) {
answer.push(op);
}
Object.assign(targetObj, {answer:JSON.stringify(answer)});
dtlList.splice(targetIndex, 1, targetObj);
Object.assign(prevState, {dtlList});
// console.info(answer, '-=-=-=-=-')
// console.info('beforeupload-------------')
form.setFieldsValue({[`${dtlId}`]:answer});
// console.info(form.getFieldsValue(), '---------')
return {...prevState};
})
}
}).catch(() => {
setLoad(prevState => ({...prevState, [`${index}`]: false}))
});
return false;
};
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