this.props.form属性

  1. [
  2. "getFieldsValue",
  3. "getFieldValue",
  4. "getFieldInstance",
  5. "setFieldsValue",
  6. "setFields",
  7. "setFieldsInitialValue",
  8. "getFieldDecorator",
  9. "getFieldProps",
  10. "getFieldsError",
  11. "getFieldError",
  12. "isFieldValidating",
  13. "isFieldsValidating",
  14. "isFieldsTouched",
  15. "isFieldTouched",
  16. "isSubmitting",
  17. "submit",
  18. "validateFields",
  19. "resetFields",
  20. "validateFieldsAndScroll"
  21. ]

image.png

获取表单的值

  1. const KEY = 'DATA'
  2. const values = form.getFieldValue(KEY); // 不带 key属性
  3. // 等价于 const valeus = form.getFieldsValue()[KEY]
  4. const valeus = form.getFieldsValue() // 带 key属性
  5. const valeus = form.getFieldsValue()[KEY]
  1. 一个 input框改变,整个 Form内的所有表单都会重新渲染
  2. 4.x不会全部渲染,只会渲染改变的表单
  3. 3.x表单优化点,拆分表单,表单不要太大,否则影响性能
  4. https://redux-form.com/

initialValue细节

form中的select初始值为空时 要用 undefined
initialValue: this.props.isTrue ? this.props.data.bank.id : undefined
form中的日期datePicker相关的日期的空,初始值要用 null
initialValue: null
select-bug.jpg

表单的重要属性

  1. value;input,textarea,select组件值为 value
  2. checked;radio,checkbox的组件值为 boolean
  3. selected;select组件下的option,不推荐的用法
    1. 推荐在select组件上使用value
  4. antd表单脚校验,把所有的数据都托管给 form
    1. data = form.getFieldValues() 获取到所有的数据
  5. 数据仅仅提供一个初始值 initialvalue
    1. form表单就像一个黑盒,不管数据如何变化,都不会影响到外面的
      1. // 只读属性,错误的用法 <Input readonly="readonly" />
      2. <Input readOnly />

直接返回值的表单

  1. InputNumber
  2. Switch
  3. Select

直接 event事件

  1. Radio
  2. Input

Form.create()()

  1. 使用Form自带的收集校验功能,需要使用Form.create()包装组件,每一个需要收集的值还需要getFieldDecorator进行注册
  2. 了解Form表单的本质,更好的使用的Form表单 ```jsx // class 组件的 Form.create class CustomForm extends React.PureComponent {}

export default Form.create({})(CustomForm)

// 函数组件的 Form.create const CustomForm =(props)=>{ const { form } = porps } export default Form.create()(CustomForm)

  1. <a name="moUdt"></a>
  2. ### mapPropsToFields
  3. ```jsx
  4. const FormContainer = Form.create({
  5. mapPropsToFields(props) {
  6. return {
  7. username: Form.createFormField({
  8. ...props.username,
  9. value: props.username.value,
  10. errors: !props.name ? [{message: '请输入名字'}] : null
  11. }),
  12. };
  13. }
  14. })(MyForm)

手动设置错误提示

  1. // 验证表单
  2. onSubmit = (e) => {
  3. e.preventDefault();
  4. this.props.form.validateFields((error, values) => {
  5. if (error) return // error=null 通过验证
  6. setTimeout(() => {
  7. // server validate
  8. if (values.user === 'yiminghe') {
  9. this.props.form.setFields({
  10. user: {
  11. value: values.user,
  12. errors: [new Error('用户名不存在')],
  13. },
  14. });
  15. }
  16. }, 500)
  17. })
  18. }
  19. render () {
  20. const { getFieldDecorator } = this.props.form
  21. return (
  22. <Form onSubmit={ this.onSubmit}>
  23. <Form.Item>
  24. { getFieldDecortator('name')(<Input placeholder=""/>) }
  25. </Form.item>
  26. </Form>
  27. )
  28. }

Form相关名词

  1. labelCol:指定label的 col宽度
  2. wrapCol:指定表单输入控件的 col宽度
  3. span跨距
  4. col应该是column的缩写,表示列
  5. wrapper包装器
  6. offset是表示偏移量,一个东西相对另一个的距离

Form.item同名问题

  1. 同名表单项的值共享,并且其中一个的值改变,另外的同名表单的值也一致改变
  2. 同名的表单被当成完全相同的表单项处理,数值和onChange行为都一样,
    1. 两个表单项的onChange始终只执行了最后一个表单项的onChange事件
    2. 动态新增表单会出现表单同名的问题
  3. Form.Item中 name的唯一性
  4. 要想得到正确的结果,应该分别分别修改两个表单中的域名

Form.Item内多个表单

Form.Item内嵌套 Form.Item多个输入框
image.png

  1. <FormItem
  2. {...formItemLayout}
  3. label={'员工信息'}
  4. >
  5. <Row gutter={16}>
  6. <Col span={8}>
  7. {this.props.form.getFieldDecorator('name', {
  8. rules: [{ required: true, message: '请输入姓名' }],
  9. })(<Input placeholder={'请输入'} />)}
  10. </Col>
  11. <Col span={8}>
  12. <FormItem>
  13. {this.props.form.getFieldDecorator('user', {
  14. rules: [{
  15. required: true,
  16. // tslint:disable-next-line:max-line-length
  17. pattern: /(^[1-9][0-9](\.\d)?$)|(^[1-9](\.\d)?$)|(^0\.\d$)/,
  18. message: '请输入收入',
  19. }],
  20. })(<Input addonAfter={'%'} placeholder={'请保留一位小数'}/>)}
  21. </FormItem>
  22. </Col>
  23. </Row>
  24. </FormItem>

Switch

  1. getFieldDecorator包裹下的Switch组件无法显示为true状态
  2. Switch组件是通过checked的属性来显示状态的,所以需要一个额外的属性 valuePropName
  3. antd Switch 报错:[antd: Switch] value is not validate prop, do you mean checked?
    1. Switch 组件是通过 checked 的属性来显示状态的,添加一个额外的属性 valuePropName
      1. <FormItem {...formItemLayout} label="是否显示">
      2. {getFieldDecorator('show', {
      3. initialValue: true,
      4. valuePropName: 'checked'
      5. })(<Switch />)}
      6. </FormItem>

valuePropName

  1. Checkbox
  2. Switch
    1. initialValue 必须是 Boolean值 true / false

checkbox.jpg

Textarea

  1. <Input.Textarea
  2. placeholder=""
  3. cols=""
  4. rows=""
  5. maxlength=""
  6. minlength=""
  7. name="textarea"
  8. />
  9. // css
  10. textarea {
  11. resize: none; // 禁止拖拽
  12. }

textarea里面内容换行

  1. 添加 <br>,或 ‘\n’ 是没有用的
  2. 利用html换行符 &#10;&#13;
  3. 普通文本换行,加上 ‘\n’ 或 ‘\r\n’

Form验证

  1. form动态校验解决方案:使用form.validateFields([key], { force: true })来解决
    1. https://github.com/ant-design/ant-design/issues/11820
  2. 自定义验证规则中必须callback一个信息回来,即每种判断情况都要加callback
  3. form组件的的 validateFields或者 validateFieldsAndScroll方法时如果它没有进入方法而是直接跳过了方法
    1. 看下:自定义验证方法validator代码块里是否有某一条分支没有执行 callback 函数
    2. 官方规定:自定义校验 callback 必须被调用
    3. 调callback(null)相当于没执行callback; callback(undefined)相当于callback()
    4. 使用 validator 时,其方法体总是需要调用 callback()
    5. https://github.com/ant-design/ant-design/issues/5155
  4. Antd组件的坑 https://segmentfault.com/a/1190000015240590
  5. Form表单封装 https://closertb.site/blog/54

Form隐藏 *

hideRequireMark
form2.jpgform1.jpg

rules 验证规则

  1. // 必须是数字
  2. rules: [
  3. {
  4. required: true,
  5. pattern: new RegExp(/^[1-9]\d*$/, 'g'),
  6. message: placeholder + number,
  7. },
  8. ]

自定义验证

  1. phoneValidator = (rule, value, callback) => {
  2. const regexp = new RegExp(/^1(3|4|5|6|7|8)\d{9}$/);
  3. if (value && !regexp.test(value)) {
  4. callback('手机号格式错误!');
  5. } else {
  6. callback();
  7. }
  8. }
  9. handleConfirmPassword(rule, value, callback) {
  10. if (value && value.length < 5 ) {
  11. return callback('长度不足');
  12. }
  13. // Note: 必须总是返回一个 callback,否则 validateFieldsAndScroll 无法响应
  14. callback()
  15. }
  16. <FormItem {...formItemLayout} label="确认密码">
  17. {
  18. getFieldDecorator('confirmPassword', {
  19. rules: [{
  20. required: true,
  21. message: '请再次输入以确认新密码',
  22. }, {
  23. validator: this.handleConfirmPassword
  24. }],
  25. })(<Input type="password" />)
  26. }
  27. </FormItem>

Form表单常见问题

验证丢失

  1. 数据存在上层组件,需要从父组件传递过来
  2. 需要从redux中回填错误信息去显示表单校验错误,否则重新渲染,丢失验证错误

动态表单嵌套

  1. 导致表单输入很卡的话,是不是页面比较复杂?
  2. 如果可以,表单部分在单独组件内与 redux connect
  3. 不需用的 props 尽量不要传递(比如少用 {…this.props} )
  4. 组件实现 shouldComponentUpdate 优化

SearchForm 搜索表单

  1. SearchForm/index.js
  2. export default SearchForm.js ```jsx import React, { Component } from ‘react’ import { Form, Input, Select, Checkbox, DatePIcker, Button } from ‘antd’

const { Item } = Form const { Option } = Select

const initOption = data => { if (!Array.isArray(data) || !data.length) return data

return data.map(item => ) }

const data = [ { type: ‘Select’, label: ‘选择城市’, labelWidth: ‘100’, field: ‘city’, initialValue: ‘’, children: [ { name: ‘lucy’, id: 1 } ], rules: [] } ]

class SearchForm extends Component {

initForm = () => { const { getFieldDecorator } = this.props.form const data = this.props.data const FormItem = []

  1. // 如果不是数组,直接返回
  2. if (!Array.isArray(data) || !data.length) return data
  3. data.forEach((item, i) => {
  4. let { type='input', label, labelWidth='90px', field,
  5. initialValue='', placeholder='', htmlType='text',
  6. children= [], } = item
  7. console.log('type', field)
  8. switch(type.toLocaleLowerCase()) {
  9. case 'select':
  10. const SELECT = (
  11. <Item label={label} key={field}>
  12. {
  13. getFieldDecorator([field], {
  14. initialValue
  15. })(
  16. <Select
  17. type='text'
  18. placeholder={placeholder}
  19. style={{width: labelWidth}}
  20. >
  21. { initOption(children) }
  22. </Select>
  23. )
  24. }
  25. </Item>
  26. )
  27. FormItem.push(SELECT)
  28. break;
  29. default:
  30. const INPUT = (
  31. <Item label={label} key={field}>
  32. {
  33. getFieldDecorator(field, {
  34. initialValue
  35. })(
  36. <Input
  37. type={htmlType}
  38. placeholder={placeholder}
  39. style={{width: labelWidth}}
  40. />
  41. )
  42. }
  43. </Item>
  44. )
  45. FormItem.push(INPUT)
  46. }
  47. })
  48. return FormItem

}

render () { return (

{ this.initForm() }
) }

// 获取 Form表单的值 onSubmit = () => { const data = this.props.form.getFieldsValue() this.props.onSubmit(data) }

onReset = ()=> { this.props.form.resetFields() } }

export default Form.create()(SearchForm)

  1. <a name="U2oty"></a>
  2. ### 使用 SearchForm.js
  3. ```jsx
  4. // 使用组件
  5. import BaseFrom from './BaseForm/index.js'
  6. <BaseForm
  7. data={ [] }
  8. onSubmit={ this.onSubmit }
  9. />

封装 v3-form

  1. import React from 'react';
  2. import {
  3. Row, Col,
  4. Form, Input, Switch, Radio,
  5. } from 'antd';
  6. // import { FormattedMessage } from 'react-intl';
  7. const options = [
  8. { label: '线上日志', value: '1' },
  9. { label: '手工输入', value: '0' },
  10. ]
  11. // 列选择
  12. const LogModalForm = props => {
  13. const {
  14. fnAction, keyFocus,
  15. } = props;
  16. const {getFieldDecorator} = props.form
  17. // getFieldDecorator('from', { initialValue: '0' });
  18. // 报错
  19. // getFieldDecorator('switch', { valuePropName:'checked', initialValue: true });
  20. const itemLayout = {
  21. labelCol: {
  22. span: 5
  23. },
  24. wrapperCol: {
  25. span: 19
  26. }
  27. }
  28. return (
  29. <>
  30. <Form {...itemLayout}>
  31. <Row gutter={16}>
  32. <Col span={12}>
  33. <Form.Item label="列名">
  34. {getFieldDecorator('columnname', {
  35. rules: [
  36. {
  37. required: true,
  38. message: 'Please input your password!',
  39. },
  40. ],
  41. })(<Input />)}
  42. </Form.Item>
  43. </Col>
  44. <Col span={12}>
  45. <Form.Item label="默认值">
  46. {getFieldDecorator('d-value', {
  47. initialValue: '',
  48. rules: [
  49. {
  50. required: true,
  51. message: 'Please input your password!',
  52. },
  53. ],
  54. })(<Input />)}
  55. </Form.Item>
  56. </Col>
  57. <Col span={12}>
  58. <Form.Item label="日志来源">
  59. {getFieldDecorator('from', {
  60. initialValue: '1',
  61. })(<Radio.Group options={options} />)}
  62. </Form.Item>
  63. </Col>
  64. <Col span={12}>
  65. <Form.Item label="选取规则">
  66. {getFieldDecorator('columnname', {})(<Input />)}
  67. </Form.Item>
  68. </Col>
  69. <Col span={12}>
  70. <Form.Item label="列值翻译">
  71. {getFieldDecorator('switch', {
  72. valuePropName:'checked',
  73. initialValue: true,
  74. })(<Switch />)}
  75. </Form.Item>
  76. </Col>
  77. </Row>
  78. </Form>
  79. </>
  80. );
  81. };
  82. // mapPropsToFields
  83. export default Form.create({
  84. name: 'logFilter',
  85. onValuesChange (props, value) {
  86. console.log('change', props, value)
  87. },
  88. })(LogModalForm)

右侧搜索框

右侧搜索框.jpg

表单设置默认值

Form.createFormField
{nickname: Form.createFormField({value: “你好呀”})}

  1. import React, { PureComponent } from 'react'
  2. import { Form, Input, Button,
  3. Select, Switch
  4. } from 'antd';
  5. const formItemLayout = {
  6. labelCol: { span: 4 },
  7. wrapperCol: { span: 8 },
  8. };
  9. const formTailLayout = {
  10. labelCol: { span: 4 },
  11. wrapperCol: { span: 8, offset: 4 },
  12. };
  13. const style = {
  14. margin: '30px auto',
  15. background: 'rgba(200, 200, 200, 0.2)',
  16. width: '800px'
  17. }
  18. // {column: "0", method: "10", diff: "0", nickname: "123", switch: true}
  19. class MyForm extends PureComponent {
  20. state = {
  21. checkNick: false,
  22. };
  23. check = () => {
  24. this.props.form.validateFields(err => {
  25. console.log('check', err)
  26. if (err) { // null 说明验证通过
  27. return console.info('表单验证失败', this.props);
  28. }
  29. const {form} = this.props
  30. const data = form.getFieldsValue()
  31. console.log('sumibt2000', data)
  32. });
  33. };
  34. handleChange = e => {
  35. this.setState(
  36. {
  37. checkNick: e.target.checked,
  38. },
  39. () => {
  40. this.props.form.validateFields(['nickname'], { force: true });
  41. },
  42. );
  43. };
  44. handleSubmit = ev => {
  45. const {form} = this.props
  46. const data = form.getFieldsValue()
  47. console.log('data', data)
  48. console.log('sumibt2000', ev, data)
  49. ev.preventDefault();
  50. return false
  51. }
  52. render() {
  53. const { getFieldDecorator } = this.props.form;
  54. return (
  55. <Form
  56. {...formItemLayout}
  57. onSubmit={this.handleSubmit}
  58. style={style}>
  59. <Form.Item label="取值列" hasFeedback>
  60. {getFieldDecorator('column', {
  61. rules: [{ required: true, message: 'Please select your country!' }],
  62. })(
  63. <Select placeholder="取值列">
  64. <Select.Option value="0">consume</Select.Option>
  65. <Select.Option value="1">provider</Select.Option>
  66. </Select>,
  67. )}
  68. </Form.Item>
  69. <Form.Item label="取值方式" hasFeedback>
  70. {getFieldDecorator('method', {
  71. rules: [{ required: true, message: 'Please select your country!' }],
  72. })(
  73. <Select placeholder="取值方式">
  74. <Select.Option value="10">当前时间的值</Select.Option>
  75. <Select.Option value="11">最近 N分钟的值</Select.Option>
  76. </Select>,
  77. )}
  78. </Form.Item>
  79. <Form.Item label="对比阈值方式" hasFeedback>
  80. {getFieldDecorator('diff', {
  81. rules: [{ required: true, message: 'Please select your country!' }],
  82. })(
  83. <Select placeholder="对比阈值方式">
  84. <Select.Option value="0">大于</Select.Option>
  85. <Select.Option value="1">小于</Select.Option>
  86. {/* {false} // noData */}
  87. </Select>,
  88. )}
  89. </Form.Item>
  90. <Form.Item {...formItemLayout} label="阈值">
  91. {
  92. getFieldDecorator('nickname', {
  93. rules: [
  94. {
  95. required: this.state.checkNick,
  96. message: '请输入阈值',
  97. // required: true,
  98. // pattern: new RegExp(/^[0-9]\d*$/, 'g'),
  99. },
  100. ],
  101. }
  102. )(<Input placeholder="请输入阈值" />)}
  103. </Form.Item>
  104. <Form.Item label="Switch">
  105. {getFieldDecorator('switch', { valuePropName: 'checked' })(<Switch />)}
  106. </Form.Item>
  107. <Form.Item {...formTailLayout}>
  108. {/* <Button type="primary" htmlType="submit"> 触发 onSubmit方法 */}
  109. <Button type="primary" onClick={this.check}>
  110. Check
  111. </Button>
  112. </Form.Item>
  113. </Form>
  114. );
  115. }
  116. }
  117. export default Form.create({
  118. name: 'my-form',
  119. mapPropsToFields (props) {
  120. console.log('props-create', props)
  121. return {
  122. // 如果是数字0,直接显示在 select框里面,不会选中
  123. column: Form.createFormField({value: "0"}),
  124. // method: "10",
  125. // diff: "0", // 报错 You must wrap field data with `createFormField`.
  126. nickname: Form.createFormField({value: "你好呀"}),
  127. switch: Form.createFormField({value: true})
  128. }
  129. }
  130. })(MyForm)
  131. /**
  132. 使用this.form.setFieldsValue时,赋值的数据要一一匹配field,
  133. 用不到的不要赋值即可,错误提示就不再提醒了
  134. */

Select

placeholder不显示问题

  1. form中的 Select初始值为空时 要用 undefined
    1. 设置 initialValue:undefined
    2. 需要设置value为undefined,placeholder可正常显示
  2. 日期datePicker相关的日期的空,初始值要用 null
  3. 回显或者新增表单数据时,空字符串 ‘’, 或者 null, 都不管用
  4. input 表单设置 ‘’ 空字符串有用

Select滚动分离

  1. 外层页面一滚动,下拉框与下拉内容就分离了,分离离了
  2. 就是因为 antd所有的弹框都是默认挂载在body下面,然后绝对定位的。所以滚动body内容, 就会造成与弹出触发的那个组件错位。
  3. 3.x以后 antd对这个bug做出了一个解决方案,就是增加了getPopupContainer属性,可以让下拉内容挂载任何存在的dom节点上,并相对其定位
  4. 案例:https://codesandbox.io/s/4j168r7jw0?file=/index.js:0-849
    1. <div style={{ position: 'relative' }} id="area">
    2. <Select
    3. defaultValue="lucy"
    4. style={{ width: 120 }}
    5. getPopupContainer={() => document.getElementById('area')}
    6. >
    7. <Option value="jack">Jack</Option>
    8. <Option value="lucy">Lucy</Option>
    9. </Select>
    10. </div>

select报错

  1. Uncaught Error: Need at least a key or a value or a label (only for OptGroup) for [object Object] at getValuePropValue (util.js:28)
    1. antd的Select组件的时候,出现报错
    2. 原因:用select组件没有写value值
    3. 至少需要一个key或者value或者是一个label
    4. Select组件里面直接渲染Option,不能再把 Option抽离为组件,否则也报错