# form

返回:开发笔记

# 手动设置表单值setFieldsValue

🔝🔝form

props.form.setFieldsValue({ safeUserPhone: option.key });
1

# You cannot set a form field before rendering a field associated with the value

🔝🔝form

WARNING

this.props.form.form.setFieldsValue这个方法里面传值的时候只能是form中用到的参数(即是getFieldDecorator方法中的field)没有的field一律不允许多传,否则就会报错

# key值动态变化,如何动态设置key的值

🔝🔝form

  • 第一种方式
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;
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
  • 第二种方式
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;
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

# 表单和数据绑定

🔝🔝form

antd 支持表单双向绑定,开发过程中无需通过 onChange()回调函数去获取组件的值,通过 getFieldDecorator() 可以自动完成数据绑定的功能。

后面括号中的组件必须自带onChange方法。。。。。。。否则会拿不到值

{
  getFieldDecorator("email", {})(<Input />);
}
1
2
3

第二个参数是 options,不同的配置可以完成更多的任务,例如必填数据验证

{
  let opt = { rules: [{ required: true, message: "the field must supply." }] };
  getFieldDecorator("email", opt)(<Input />);
}
1
2
3
4

也可以完成更多业务逻辑数据验证,例如:

{
  let opt = {
    rules: [{ type: "email", message: "It's invalid email address." }]
  };
  getFieldDecorator("email", opt)(<Input />);
}
1
2
3
4
5
6

还可以指定一个初始值:

{
  let opt = { initialValue: "hello@mail.com" };
  getFieldDecorator("email", opt)(<Input />);
}
1
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)
  );
}
1
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 表单初始值。
例如:新增功能,清空和重置就是一样的效果,而对于编辑,清空就是把初始值都清空掉,重置就是恢复刚开始的初始值。

//清空
form.setFieldsValue({"fieldName": ""});

//重置
form.resetFields();
1
2
3
4
5

# 自定义组件值

🔝🔝form

项目实例:对 antd RangePicker 抽取完独立组件后,form 表单获取不到值
自定义组件被 getFieldsValue 包裹,会获得以下属性:
onChange方法, 子组件调用此方法,可将值传给父组件,从而Form可拿到自定义组件的值 value属性,获得初始值

<Form.Item label="发送时间">
  {getFieldDecorator('range-time-picker', {
    rules: [{ required: false, message: '请输入开始时间-结束时间' }],
  })(
    <RangePickerPage />
  )}
</Form.Item>
1
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;
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

# 防抖

🔝🔝form

例如:表单字段,密码和确认密码,改变 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>
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

# 直接使用 rc-form 库 createForm,与 antd Form 的 Form.create() 设置样式不同

🔝🔝form

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) // !!!
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

# Form initialValue 值编辑后,表单的值不改变问题

🔝🔝form

  • 方法一
    其实,只要编辑成功后,回调调用 form.resetFields(),就可以了,如果是使用 modal 框弹出的表单,就可以直接使用 destroyOnClose = {true} 属性。
  • 方法二
    如果是 class 类,可以使用钩子。
  componentDidUpdate = (prevProps, prevState) => {
    if (!prevProps.visible) {
      prevProps.form.resetFields();
    }
  };
1
2
3
4
5

# Form.create 方式

🔝🔝form

  • 方式一:@注解
@Form.create()
1
  • 方式二:高阶写法
export default (Form.create({})(APP));
1

# form 表单提交 htmlType,改为 onClick

🔝🔝form

<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>
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

# form.validateFields 直接获取表单的值

🔝🔝form

this.props.form.validateFields((err, fieldsValue) => {
  if (err) return;
  this.handleSubmit(fieldsValue);
});
1
2
3
4

# 利用 validator 和正则,验证中文

🔝🔝form

<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();
    });
  }
};
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

# form 表单,FormItem 的 rules 中新增 validator,实时请求校验

🔝🔝form

<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();
  });
};
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
// 实际项目
  // 校验是否选择了省份和市区
  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>
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

# form 表单中 FormItem 的布局

🔝🔝form

使用 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>
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

# 必填校验时

默认校验的属性是:"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>
            )}
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

上传完成后还需做可链接处理

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;
  };
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