Form表单

    • 用于创建一个实体或收集信息。

    • 需要对输入的数据类型进行校验时。

    表单

    我们为 提供了以下三种排列方式:

    • 水平排列:标签和表单控件水平排列;(默认)

    • 垂直排列:标签和表单控件上下垂直排列;

    • 行内排列:表单项水平行内排列。

    表单一定会包含表单域,表单域可以是输入控件,标准表单域,标签,下拉菜单,文本域等。

    这里我们封装了表单域 <Form.Item />

    代码演示

    水平登录栏

    水平登录栏,常用在顶部导航栏中。

    1. import { Form, Icon, Input, Button } from 'antd';
    2. function hasErrors(fieldsError) {
    3. return Object.keys(fieldsError).some(field => fieldsError[field]);
    4. }
    5. class HorizontalLoginForm extends React.Component {
    6. componentDidMount() {
    7. // To disabled submit button at the beginning.
    8. this.props.form.validateFields();
    9. }
    10. handleSubmit = e => {
    11. e.preventDefault();
    12. this.props.form.validateFields((err, values) => {
    13. if (!err) {
    14. console.log('Received values of form: ', values);
    15. }
    16. });
    17. };
    18. render() {
    19. const { getFieldDecorator, getFieldsError, getFieldError, isFieldTouched } = this.props.form;
    20. // Only show error after a field is touched.
    21. const usernameError = isFieldTouched('username') && getFieldError('username');
    22. const passwordError = isFieldTouched('password') && getFieldError('password');
    23. return (
    24. <Form layout="inline" onSubmit={this.handleSubmit}>
    25. <Form.Item validateStatus={usernameError ? 'error' : ''} help={usernameError || ''}>
    26. {getFieldDecorator('username', {
    27. rules: [{ required: true, message: 'Please input your username!' }],
    28. })(
    29. <Input
    30. prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
    31. placeholder="Username"
    32. />,
    33. )}
    34. </Form.Item>
    35. <Form.Item validateStatus={passwordError ? 'error' : ''} help={passwordError || ''}>
    36. {getFieldDecorator('password', {
    37. rules: [{ required: true, message: 'Please input your Password!' }],
    38. })(
    39. <Input
    40. prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
    41. type="password"
    42. placeholder="Password"
    43. />,
    44. )}
    45. </Form.Item>
    46. <Form.Item>
    47. <Button type="primary" htmlType="submit" disabled={hasErrors(getFieldsError())}>
    48. Log in
    49. </Button>
    50. </Form.Item>
    51. </Form>
    52. );
    53. }
    54. }
    55. const WrappedHorizontalLoginForm = Form.create({ name: 'horizontal_login' })(HorizontalLoginForm);
    56. ReactDOM.render(<WrappedHorizontalLoginForm />, mountNode);

    Form表单 - 图2

    普通的登录框,可以容纳更多的元素。

    1. import { Form, Icon, Input, Button, Checkbox } from 'antd';
    2. class NormalLoginForm extends React.Component {
    3. handleSubmit = e => {
    4. e.preventDefault();
    5. this.props.form.validateFields((err, values) => {
    6. if (!err) {
    7. console.log('Received values of form: ', values);
    8. }
    9. });
    10. };
    11. render() {
    12. const { getFieldDecorator } = this.props.form;
    13. return (
    14. <Form onSubmit={this.handleSubmit} className="login-form">
    15. <Form.Item>
    16. {getFieldDecorator('username', {
    17. rules: [{ required: true, message: 'Please input your username!' }],
    18. })(
    19. <Input
    20. prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
    21. placeholder="Username"
    22. />,
    23. )}
    24. </Form.Item>
    25. <Form.Item>
    26. {getFieldDecorator('password', {
    27. rules: [{ required: true, message: 'Please input your Password!' }],
    28. })(
    29. <Input
    30. prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
    31. type="password"
    32. placeholder="Password"
    33. />,
    34. )}
    35. </Form.Item>
    36. <Form.Item>
    37. {getFieldDecorator('remember', {
    38. valuePropName: 'checked',
    39. initialValue: true,
    40. })(<Checkbox>Remember me</Checkbox>)}
    41. <a className="login-form-forgot" href="">
    42. Forgot password
    43. </a>
    44. <Button type="primary" htmlType="submit" className="login-form-button">
    45. Log in
    46. </Button>
    47. Or <a href="">register now!</a>
    48. </Form.Item>
    49. </Form>
    50. );
    51. }
    52. }
    53. const WrappedNormalLoginForm = Form.create({ name: 'normal_login' })(NormalLoginForm);
    54. ReactDOM.render(<WrappedNormalLoginForm />, mountNode);
    1. #components-form-demo-normal-login .login-form {
    2. max-width: 300px;
    3. }
    4. #components-form-demo-normal-login .login-form-forgot {
    5. float: right;
    6. }
    7. #components-form-demo-normal-login .login-form-button {
    8. width: 100%;
    9. }

    注册新用户

    用户填写必须的信息以注册新用户。

    1. import {
    2. Form,
    3. Input,
    4. Tooltip,
    5. Icon,
    6. Cascader,
    7. Select,
    8. Row,
    9. Col,
    10. Checkbox,
    11. Button,
    12. AutoComplete,
    13. } from 'antd';
    14. const { Option } = Select;
    15. const AutoCompleteOption = AutoComplete.Option;
    16. const residences = [
    17. value: 'zhejiang',
    18. label: 'Zhejiang',
    19. children: [
    20. {
    21. value: 'hangzhou',
    22. label: 'Hangzhou',
    23. children: [
    24. {
    25. value: 'xihu',
    26. label: 'West Lake',
    27. },
    28. ],
    29. },
    30. ],
    31. },
    32. {
    33. value: 'jiangsu',
    34. label: 'Jiangsu',
    35. children: [
    36. {
    37. value: 'nanjing',
    38. label: 'Nanjing',
    39. children: [
    40. {
    41. value: 'zhonghuamen',
    42. label: 'Zhong Hua Men',
    43. },
    44. ],
    45. },
    46. ],
    47. },
    48. ];
    49. class RegistrationForm extends React.Component {
    50. state = {
    51. confirmDirty: false,
    52. autoCompleteResult: [],
    53. };
    54. handleSubmit = e => {
    55. this.props.form.validateFieldsAndScroll((err, values) => {
    56. if (!err) {
    57. console.log('Received values of form: ', values);
    58. }
    59. });
    60. };
    61. handleConfirmBlur = e => {
    62. const { value } = e.target;
    63. this.setState({ confirmDirty: this.state.confirmDirty || !!value });
    64. };
    65. compareToFirstPassword = (rule, value, callback) => {
    66. const { form } = this.props;
    67. if (value && value !== form.getFieldValue('password')) {
    68. callback('Two passwords that you enter is inconsistent!');
    69. } else {
    70. callback();
    71. }
    72. };
    73. validateToNextPassword = (rule, value, callback) => {
    74. const { form } = this.props;
    75. if (value && this.state.confirmDirty) {
    76. form.validateFields(['confirm'], { force: true });
    77. }
    78. callback();
    79. };
    80. handleWebsiteChange = value => {
    81. let autoCompleteResult;
    82. if (!value) {
    83. autoCompleteResult = [];
    84. } else {
    85. autoCompleteResult = ['.com', '.org', '.net'].map(domain => `${value}${domain}`);
    86. }
    87. this.setState({ autoCompleteResult });
    88. };
    89. render() {
    90. const { getFieldDecorator } = this.props.form;
    91. const { autoCompleteResult } = this.state;
    92. const formItemLayout = {
    93. labelCol: {
    94. xs: { span: 24 },
    95. sm: { span: 8 },
    96. },
    97. wrapperCol: {
    98. xs: { span: 24 },
    99. sm: { span: 16 },
    100. },
    101. };
    102. const tailFormItemLayout = {
    103. wrapperCol: {
    104. xs: {
    105. span: 24,
    106. offset: 0,
    107. },
    108. sm: {
    109. span: 16,
    110. offset: 8,
    111. },
    112. },
    113. };
    114. const prefixSelector = getFieldDecorator('prefix', {
    115. initialValue: '86',
    116. })(
    117. <Select style={{ width: 70 }}>
    118. <Option value="86">+86</Option>
    119. <Option value="87">+87</Option>
    120. </Select>,
    121. );
    122. const websiteOptions = autoCompleteResult.map(website => (
    123. <AutoCompleteOption key={website}>{website}</AutoCompleteOption>
    124. ));
    125. return (
    126. <Form {...formItemLayout} onSubmit={this.handleSubmit}>
    127. <Form.Item label="E-mail">
    128. {getFieldDecorator('email', {
    129. rules: [
    130. {
    131. type: 'email',
    132. message: 'The input is not valid E-mail!',
    133. },
    134. {
    135. required: true,
    136. message: 'Please input your E-mail!',
    137. },
    138. ],
    139. })(<Input />)}
    140. </Form.Item>
    141. <Form.Item label="Password" hasFeedback>
    142. {getFieldDecorator('password', {
    143. rules: [
    144. {
    145. required: true,
    146. message: 'Please input your password!',
    147. },
    148. {
    149. validator: this.validateToNextPassword,
    150. },
    151. ],
    152. })(<Input.Password />)}
    153. </Form.Item>
    154. <Form.Item label="Confirm Password" hasFeedback>
    155. {getFieldDecorator('confirm', {
    156. rules: [
    157. {
    158. required: true,
    159. message: 'Please confirm your password!',
    160. },
    161. {
    162. validator: this.compareToFirstPassword,
    163. },
    164. ],
    165. })(<Input.Password onBlur={this.handleConfirmBlur} />)}
    166. </Form.Item>
    167. <Form.Item
    168. label={
    169. <span>
    170. Nickname&nbsp;
    171. <Tooltip title="What do you want others to call you?">
    172. <Icon type="question-circle-o" />
    173. </Tooltip>
    174. </span>
    175. >
    176. {getFieldDecorator('nickname', {
    177. rules: [{ required: true, message: 'Please input your nickname!', whitespace: true }],
    178. })(<Input />)}
    179. </Form.Item>
    180. <Form.Item label="Habitual Residence">
    181. {getFieldDecorator('residence', {
    182. initialValue: ['zhejiang', 'hangzhou', 'xihu'],
    183. rules: [
    184. { type: 'array', required: true, message: 'Please select your habitual residence!' },
    185. ],
    186. })(<Cascader options={residences} />)}
    187. </Form.Item>
    188. <Form.Item label="Phone Number">
    189. {getFieldDecorator('phone', {
    190. rules: [{ required: true, message: 'Please input your phone number!' }],
    191. })(<Input addonBefore={prefixSelector} style={{ width: '100%' }} />)}
    192. </Form.Item>
    193. <Form.Item label="Website">
    194. {getFieldDecorator('website', {
    195. rules: [{ required: true, message: 'Please input website!' }],
    196. })(
    197. <AutoComplete
    198. dataSource={websiteOptions}
    199. onChange={this.handleWebsiteChange}
    200. placeholder="website"
    201. >
    202. <Input />
    203. </AutoComplete>,
    204. )}
    205. </Form.Item>
    206. <Form.Item label="Captcha" extra="We must make sure that your are a human.">
    207. <Row gutter={8}>
    208. <Col span={12}>
    209. {getFieldDecorator('captcha', {
    210. rules: [{ required: true, message: 'Please input the captcha you got!' }],
    211. })(<Input />)}
    212. </Col>
    213. <Col span={12}>
    214. <Button>Get captcha</Button>
    215. </Col>
    216. </Row>
    217. </Form.Item>
    218. <Form.Item {...tailFormItemLayout}>
    219. {getFieldDecorator('agreement', {
    220. valuePropName: 'checked',
    221. })(
    222. <Checkbox>
    223. I have read the <a href="">agreement</a>
    224. </Checkbox>,
    225. )}
    226. </Form.Item>
    227. <Form.Item {...tailFormItemLayout}>
    228. <Button type="primary" htmlType="submit">
    229. Register
    230. </Button>
    231. </Form.Item>
    232. </Form>
    233. );
    234. }
    235. }
    236. const WrappedRegistrationForm = Form.create({ name: 'register' })(RegistrationForm);
    237. ReactDOM.render(<WrappedRegistrationForm />, mountNode);

    Form表单 - 图4

    三列栅格式的表单排列方式,常用于数据表格的高级搜索。

    有部分定制的样式代码,由于输入标签长度不确定,需要根据具体情况自行调整。

    1. import { Form, Row, Col, Input, Button, Icon } from 'antd';
    2. class AdvancedSearchForm extends React.Component {
    3. state = {
    4. expand: false,
    5. };
    6. // To generate mock Form.Item
    7. getFields() {
    8. const count = this.state.expand ? 10 : 6;
    9. const { getFieldDecorator } = this.props.form;
    10. const children = [];
    11. for (let i = 0; i < 10; i++) {
    12. children.push(
    13. <Col span={8} key={i} style={{ display: i < count ? 'block' : 'none' }}>
    14. <Form.Item label={`Field ${i}`}>
    15. {getFieldDecorator(`field-${i}`, {
    16. rules: [
    17. {
    18. required: true,
    19. message: 'Input something!',
    20. },
    21. ],
    22. })(<Input placeholder="placeholder" />)}
    23. </Form.Item>
    24. </Col>,
    25. );
    26. }
    27. return children;
    28. }
    29. handleSearch = e => {
    30. e.preventDefault();
    31. this.props.form.validateFields((err, values) => {
    32. console.log('Received values of form: ', values);
    33. });
    34. };
    35. handleReset = () => {
    36. this.props.form.resetFields();
    37. };
    38. toggle = () => {
    39. const { expand } = this.state;
    40. this.setState({ expand: !expand });
    41. };
    42. render() {
    43. return (
    44. <Form className="ant-advanced-search-form" onSubmit={this.handleSearch}>
    45. <Row gutter={24}>{this.getFields()}</Row>
    46. <Row>
    47. <Col span={24} style={{ textAlign: 'right' }}>
    48. <Button type="primary" htmlType="submit">
    49. Search
    50. </Button>
    51. <Button style={{ marginLeft: 8 }} onClick={this.handleReset}>
    52. Clear
    53. </Button>
    54. <a style={{ marginLeft: 8, fontSize: 12 }} onClick={this.toggle}>
    55. Collapse <Icon type={this.state.expand ? 'up' : 'down'} />
    56. </a>
    57. </Col>
    58. </Row>
    59. </Form>
    60. );
    61. }
    62. }
    63. const WrappedAdvancedSearchForm = Form.create({ name: 'advanced_search' })(AdvancedSearchForm);
    64. ReactDOM.render(
    65. <div>
    66. <WrappedAdvancedSearchForm />
    67. <div className="search-result-list">Search Result List</div>
    68. </div>,
    69. mountNode,
    70. );
    1. .ant-advanced-search-form {
    2. padding: 24px;
    3. background: #fbfbfb;
    4. border: 1px solid #d9d9d9;
    5. border-radius: 6px;
    6. }
    7. .ant-advanced-search-form .ant-form-item {
    8. display: flex;
    9. }
    10. .ant-advanced-search-form .ant-form-item-control-wrapper {
    11. flex: 1;
    12. }

    弹出层中的新建表单

    当用户访问一个展示了某个列表的页面,想新建一项但又不想跳转页面时,可以用 Modal 弹出一个表单,用户填写必要信息后创建新的项。

    import { Button, Modal, Form, Input, Radio } from 'antd';
    
    const CollectionCreateForm = Form.create({ name: 'form_in_modal' })(
      // eslint-disable-next-line
      class extends React.Component {
        render() {
          const { visible, onCancel, onCreate, form } = this.props;
          const { getFieldDecorator } = form;
          return (
            <Modal
              visible={visible}
              title="Create a new collection"
              okText="Create"
              onCancel={onCancel}
              onOk={onCreate}
            >
              <Form layout="vertical">
                <Form.Item label="Title">
                  {getFieldDecorator('title', {
                    rules: [{ required: true, message: 'Please input the title of collection!' }],
                  })(<Input />)}
                </Form.Item>
                <Form.Item label="Description">
                  {getFieldDecorator('description')(<Input type="textarea" />)}
                </Form.Item>
                <Form.Item className="collection-create-form_last-form-item">
                  {getFieldDecorator('modifier', {
                    initialValue: 'public',
                  })(
                    <Radio.Group>
                      <Radio value="public">Public</Radio>
                      <Radio value="private">Private</Radio>
                    </Radio.Group>,
                  )}
                </Form.Item>
              </Form>
            </Modal>
          );
        }
      },
    );
    
    class CollectionsPage extends React.Component {
      state = {
        visible: false,
      };
    
      showModal = () => {
        this.setState({ visible: true });
      };
    
      handleCancel = () => {
        this.setState({ visible: false });
      };
    
      handleCreate = () => {
        const { form } = this.formRef.props;
        form.validateFields((err, values) => {
          if (err) {
            return;
          }
    
          console.log('Received values of form: ', values);
          form.resetFields();
          this.setState({ visible: false });
        });
      };
    
      saveFormRef = formRef => {
        this.formRef = formRef;
      };
    
      render() {
        return (
          <div>
            <Button type="primary" onClick={this.showModal}>
              New Collection
            </Button>
            <CollectionCreateForm
              wrappedComponentRef={this.saveFormRef}
              visible={this.state.visible}
              onCancel={this.handleCancel}
              onCreate={this.handleCreate}
            />
          </div>
        );
      }
    }
    
    ReactDOM.render(<CollectionsPage />, mountNode);
    .collection-create-form_last-form-item {
      margin-bottom: 0;
    }

    Form表单 - 图6

    动态增加、减少表单项。

    .dynamic-delete-button {
      cursor: pointer;
      position: relative;
      top: 4px;
      font-size: 24px;
      color: #999;
      transition: all 0.3s;
    }
    .dynamic-delete-button:hover {
      color: #777;
    }
    .dynamic-delete-button[disabled] {
      cursor: not-allowed;
      opacity: 0.5;
    }

    时间类组件的 value 类型为 moment 对象,所以在提交服务器前需要预处理。

    import { Form, DatePicker, TimePicker, Button } from 'antd';
    
    const { MonthPicker, RangePicker } = DatePicker;
    
    class TimeRelatedForm extends React.Component {
      handleSubmit = e => {
        e.preventDefault();
    
        this.props.form.validateFields((err, fieldsValue) => {
          if (err) {
            return;
          }
    
          // Should format date value before submit.
          const rangeValue = fieldsValue['range-picker'];
          const rangeTimeValue = fieldsValue['range-time-picker'];
          const values = {
            ...fieldsValue,
            'date-picker': fieldsValue['date-picker'].format('YYYY-MM-DD'),
            'date-time-picker': fieldsValue['date-time-picker'].format('YYYY-MM-DD HH:mm:ss'),
            'month-picker': fieldsValue['month-picker'].format('YYYY-MM'),
            'range-picker': [rangeValue[0].format('YYYY-MM-DD'), rangeValue[1].format('YYYY-MM-DD')],
            'range-time-picker': [
              rangeTimeValue[0].format('YYYY-MM-DD HH:mm:ss'),
              rangeTimeValue[1].format('YYYY-MM-DD HH:mm:ss'),
            ],
            'time-picker': fieldsValue['time-picker'].format('HH:mm:ss'),
          };
          console.log('Received values of form: ', values);
        });
      };
    
      render() {
        const { getFieldDecorator } = this.props.form;
        const formItemLayout = {
          labelCol: {
            xs: { span: 24 },
            sm: { span: 8 },
          },
          wrapperCol: {
            xs: { span: 24 },
            sm: { span: 16 },
          },
        };
        const config = {
          rules: [{ type: 'object', required: true, message: 'Please select time!' }],
        };
        const rangeConfig = {
          rules: [{ type: 'array', required: true, message: 'Please select time!' }],
        };
        return (
          <Form {...formItemLayout} onSubmit={this.handleSubmit}>
            <Form.Item label="DatePicker">
              {getFieldDecorator('date-picker', config)(<DatePicker />)}
            </Form.Item>
            <Form.Item label="DatePicker[showTime]">
              {getFieldDecorator('date-time-picker', config)(
                <DatePicker showTime format="YYYY-MM-DD HH:mm:ss" />,
              )}
            </Form.Item>
            <Form.Item label="MonthPicker">
              {getFieldDecorator('month-picker', config)(<MonthPicker />)}
            </Form.Item>
            <Form.Item label="RangePicker">
              {getFieldDecorator('range-picker', rangeConfig)(<RangePicker />)}
            </Form.Item>
            <Form.Item label="RangePicker[showTime]">
              {getFieldDecorator('range-time-picker', rangeConfig)(
                <RangePicker showTime format="YYYY-MM-DD HH:mm:ss" />,
              )}
            </Form.Item>
            <Form.Item label="TimePicker">
              {getFieldDecorator('time-picker', config)(<TimePicker />)}
            </Form.Item>
            <Form.Item
              wrapperCol={{
                xs: { span: 24, offset: 0 },
                sm: { span: 16, offset: 8 },
              }}
            >
              <Button type="primary" htmlType="submit">
                Submit
              </Button>
            </Form.Item>
          </Form>
        );
      }
    }
    
    const WrappedTimeRelatedForm = Form.create({ name: 'time_related_controls' })(TimeRelatedForm);
    
    ReactDOM.render(<WrappedTimeRelatedForm />, mountNode);

    Form表单 - 图8

    自定义表单控件

    自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件遵循以下的约定:

    import { Form, Input, Select, Button } from 'antd';
    
    const { Option } = Select;
    
    class PriceInput extends React.Component {
      static getDerivedStateFromProps(nextProps) {
        // Should be a controlled component.
        if ('value' in nextProps) {
          return {
            ...(nextProps.value || {}),
          };
        }
        return null;
      }
    
      constructor(props) {
        super(props);
    
        const value = props.value || {};
        this.state = {
          number: value.number || 0,
          currency: value.currency || 'rmb',
        };
      }
    
      handleNumberChange = e => {
        const number = parseInt(e.target.value || 0, 10);
        if (Number.isNaN(number)) {
          return;
        }
        if (!('value' in this.props)) {
          this.setState({ number });
        }
        this.triggerChange({ number });
      };
    
      handleCurrencyChange = currency => {
        if (!('value' in this.props)) {
          this.setState({ currency });
        }
        this.triggerChange({ currency });
      };
    
      triggerChange = changedValue => {
        // Should provide an event to pass value to Form.
        const { onChange } = this.props;
        if (onChange) {
          onChange(Object.assign({}, this.state, changedValue));
        }
      };
    
      render() {
        const { size } = this.props;
        const { state } = this;
        return (
          <span>
            <Input
              type="text"
              size={size}
              value={state.number}
              onChange={this.handleNumberChange}
              style={{ width: '65%', marginRight: '3%' }}
            />
            <Select
              value={state.currency}
              size={size}
              style={{ width: '32%' }}
              onChange={this.handleCurrencyChange}
            >
              <Option value="rmb">RMB</Option>
              <Option value="dollar">Dollar</Option>
            </Select>
          </span>
        );
      }
    }
    
    class Demo extends React.Component {
      handleSubmit = e => {
        e.preventDefault();
        this.props.form.validateFields((err, values) => {
          if (!err) {
            console.log('Received values of form: ', values);
          }
        });
      };
    
      checkPrice = (rule, value, callback) => {
        if (value.number > 0) {
          callback();
          return;
        }
        callback('Price must greater than zero!');
      };
    
      render() {
        const { getFieldDecorator } = this.props.form;
        return (
          <Form layout="inline" onSubmit={this.handleSubmit}>
            <Form.Item label="Price">
              {getFieldDecorator('price', {
                initialValue: { number: 0, currency: 'rmb' },
                rules: [{ validator: this.checkPrice }],
              })(<PriceInput />)}
            </Form.Item>
            <Form.Item>
              <Button type="primary" htmlType="submit">
                Submit
              </Button>
            </Form.Item>
          </Form>
        );
      }
    }
    
    const WrappedDemo = Form.create({ name: 'customized_form_controls' })(Demo);
    
    ReactDOM.render(<WrappedDemo />, mountNode);

    通过使用 onFieldsChangemapPropsToFields,可以把表单的数据存储到上层组件或者 Redux、 中,更多可参考 rc-form 示例

    注意:mapPropsToFields 里面返回的表单域数据必须使用 Form.createFormField 包装。

    import { Form, Input } from 'antd';
    
    const CustomizedForm = Form.create({
      name: 'global_state',
      onFieldsChange(props, changedFields) {
        props.onChange(changedFields);
      },
      mapPropsToFields(props) {
        return {
          username: Form.createFormField({
            ...props.username,
            value: props.username.value,
          }),
        };
      },
      onValuesChange(_, values) {
        console.log(values);
      },
    })(props => {
      const { getFieldDecorator } = props.form;
      return (
        <Form layout="inline">
          <Form.Item label="Username">
            {getFieldDecorator('username', {
              rules: [{ required: true, message: 'Username is required!' }],
            })(<Input />)}
          </Form.Item>
        </Form>
      );
    });
    
    class Demo extends React.Component {
      state = {
        fields: {
          username: {
            value: 'benjycui',
          },
        },
      };
    
      handleFormChange = changedFields => {
        this.setState(({ fields }) => ({
          fields: { ...fields, ...changedFields },
        }));
      };
    
      render() {
        const { fields } = this.state;
        return (
          <div>
            <CustomizedForm {...fields} onChange={this.handleFormChange} />
            <pre className="language-bash">{JSON.stringify(fields, null, 2)}</pre>
          </div>
        );
      }
    }
    
    ReactDOM.render(<Demo />, mountNode);

    Form表单 - 图10

    使用 Form.create 处理后的表单具有自动收集数据并校验的功能,但如果您不需要这个功能,或者默认的行为无法满足业务需求,可以选择不使用 Form.create 并自行处理数据。

    import { Form, InputNumber } from 'antd';
    
    function validatePrimeNumber(number) {
      if (number === 11) {
        return {
          validateStatus: 'success',
          errorMsg: null,
        };
      }
      return {
        validateStatus: 'error',
        errorMsg: 'The prime between 8 and 12 is 11!',
      };
    }
    
    class RawForm extends React.Component {
      state = {
        number: {
          value: 11,
        },
      };
    
      handleNumberChange = value => {
        this.setState({
          number: {
            ...validatePrimeNumber(value),
            value,
          },
        });
      };
    
      render() {
        const formItemLayout = {
          labelCol: { span: 7 },
          wrapperCol: { span: 12 },
        };
        const { number } = this.state;
        const tips =
          'A prime is a natural number greater than 1 that has no positive divisors other than 1 and itself.';
        return (
          <Form>
            <Form.Item
              {...formItemLayout}
              label="Prime between 8 & 12"
              validateStatus={number.validateStatus}
              help={number.errorMsg || tips}
            >
              <InputNumber min={8} max={12} value={number.value} onChange={this.handleNumberChange} />
            </Form.Item>
          </Form>
        );
      }
    }
    
    ReactDOM.render(<RawForm />, mountNode);

    自定义校验

    我们提供了 validateStatus help hasFeedback 等属性,你可以不需要使用 Form.creategetFieldDecorator,自己定义校验的时机和内容。

    • validateStatus: 校验状态,可选 'success', 'warning', 'error', 'validating'。

    • hasFeedback:用于给输入框添加反馈图标。

    • help:设置校验文案。

    import { Form, Input, DatePicker, TimePicker, Select, Cascader, InputNumber } from 'antd';
    
    const { Option } = Select;
    
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 5 },
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 12 },
      },
    };
    
    ReactDOM.render(
      <Form {...formItemLayout}>
        <Form.Item
          label="Fail"
          validateStatus="error"
          help="Should be combination of numbers & alphabets"
        >
          <Input placeholder="unavailable choice" id="error" />
        </Form.Item>
    
        <Form.Item label="Warning" validateStatus="warning">
          <Input placeholder="Warning" id="warning" />
        </Form.Item>
    
        <Form.Item
          label="Validating"
          hasFeedback
          validateStatus="validating"
          help="The information is being validated..."
        >
          <Input placeholder="I'm the content is being validated" id="validating" />
        </Form.Item>
    
        <Form.Item label="Success" hasFeedback validateStatus="success">
          <Input placeholder="I'm the content" id="success" />
        </Form.Item>
    
        <Form.Item label="Warning" hasFeedback validateStatus="warning">
          <Input placeholder="Warning" id="warning2" />
        </Form.Item>
    
        <Form.Item
          label="Fail"
          hasFeedback
          validateStatus="error"
          help="Should be combination of numbers & alphabets"
        >
          <Input placeholder="unavailable choice" id="error2" />
        </Form.Item>
    
        <Form.Item label="Success" hasFeedback validateStatus="success">
          <DatePicker style={{ width: '100%' }} />
        </Form.Item>
    
        <Form.Item label="Warning" hasFeedback validateStatus="warning">
          <TimePicker style={{ width: '100%' }} />
        </Form.Item>
    
        <Form.Item label="Error" hasFeedback validateStatus="error">
          <Select defaultValue="1">
            <Option value="1">Option 1</Option>
            <Option value="2">Option 2</Option>
            <Option value="3">Option 3</Option>
          </Select>
        </Form.Item>
    
        <Form.Item
          label="Validating"
          hasFeedback
          validateStatus="validating"
          help="The information is being validated..."
        >
          <Cascader defaultValue={['1']} options={[]} />
        </Form.Item>
    
        <Form.Item label="inline" style={{ marginBottom: 0 }}>
          <Form.Item
            validateStatus="error"
            help="Please select the correct date"
            style={{ display: 'inline-block', width: 'calc(50% - 12px)' }}
          >
            <DatePicker />
          </Form.Item>
          <span style={{ display: 'inline-block', width: '24px', textAlign: 'center' }}>-</span>
          <Form.Item style={{ display: 'inline-block', width: 'calc(50% - 12px)' }}>
            <DatePicker />
          </Form.Item>
        </Form.Item>
    
        <Form.Item label="Success" hasFeedback validateStatus="success">
          <InputNumber style={{ width: '100%' }} />
        </Form.Item>
      </Form>,
      mountNode,
    );

    使用 setFieldsValue 来动态设置其他控件的值。

    import { Form, Select, Input, Button } from 'antd';
    
    const { Option } = Select;
    
    class App extends React.Component {
      handleSubmit = e => {
        e.preventDefault();
        this.props.form.validateFields((err, values) => {
          if (!err) {
            console.log('Received values of form: ', values);
          }
        });
      };
    
      handleSelectChange = value => {
        console.log(value);
        this.props.form.setFieldsValue({
          note: `Hi, ${value === 'male' ? 'man' : 'lady'}!`,
        });
      };
    
      render() {
        const { getFieldDecorator } = this.props.form;
        return (
          <Form labelCol={{ span: 5 }} wrapperCol={{ span: 12 }} onSubmit={this.handleSubmit}>
            <Form.Item label="Note">
              {getFieldDecorator('note', {
                rules: [{ required: true, message: 'Please input your note!' }],
              })(<Input />)}
            </Form.Item>
            <Form.Item label="Gender">
              {getFieldDecorator('gender', {
                rules: [{ required: true, message: 'Please select your gender!' }],
              })(
                <Select
                  placeholder="Select a option and change input text above"
                  onChange={this.handleSelectChange}
                >
                  <Option value="male">male</Option>
                  <Option value="female">female</Option>
                </Select>,
              )}
            </Form.Item>
            <Form.Item wrapperCol={{ span: 12, offset: 5 }}>
              <Button type="primary" htmlType="submit">
                Submit
              </Button>
            </Form.Item>
          </Form>
        );
      }
    }
    
    const WrappedApp = Form.create({ name: 'coordinated' })(App);
    
    ReactDOM.render(<WrappedApp />, mountNode);

    Form表单 - 图13

    表单布局

    表单有三种布局。

    import { Form, Input, Button, Radio } from 'antd';
    
    class FormLayoutDemo extends React.Component {
      constructor() {
        super();
        this.state = {
          formLayout: 'horizontal',
        };
      }
    
      handleFormLayoutChange = e => {
        this.setState({ formLayout: e.target.value });
      };
    
      render() {
        const { formLayout } = this.state;
        const formItemLayout =
          formLayout === 'horizontal'
            ? {
                labelCol: { span: 4 },
                wrapperCol: { span: 14 },
              }
            : null;
        const buttonItemLayout =
          formLayout === 'horizontal'
            ? {
                wrapperCol: { span: 14, offset: 4 },
              }
            : null;
        return (
          <div>
            <Form layout={formLayout}>
              <Form.Item label="Form Layout" {...formItemLayout}>
                <Radio.Group defaultValue="horizontal" onChange={this.handleFormLayoutChange}>
                  <Radio.Button value="horizontal">Horizontal</Radio.Button>
                  <Radio.Button value="vertical">Vertical</Radio.Button>
                  <Radio.Button value="inline">Inline</Radio.Button>
                </Radio.Group>
              </Form.Item>
              <Form.Item label="Field A" {...formItemLayout}>
                <Input placeholder="input placeholder" />
              </Form.Item>
              <Form.Item label="Field B" {...formItemLayout}>
                <Input placeholder="input placeholder" />
              </Form.Item>
              <Form.Item {...buttonItemLayout}>
                <Button type="primary">Submit</Button>
              </Form.Item>
            </Form>
          </div>
        );
      }
    }
    
    ReactDOM.render(<FormLayoutDemo />, mountNode);

    根据不同情况执行不同的校验规则。

    Form表单 - 图15

    校验其他组件

    以上演示没有出现的表单控件对应的校验演示。

    import {
      Form,
      Select,
      InputNumber,
      Switch,
      Radio,
      Slider,
      Button,
      Upload,
      Icon,
      Rate,
      Checkbox,
      Row,
      Col,
    } from 'antd';
    
    const { Option } = Select;
    
    class Demo extends React.Component {
      handleSubmit = e => {
        e.preventDefault();
        this.props.form.validateFields((err, values) => {
          if (!err) {
            console.log('Received values of form: ', values);
          }
        });
      };
    
      normFile = e => {
        console.log('Upload event:', e);
        if (Array.isArray(e)) {
          return e;
        }
        return e && e.fileList;
      };
    
      render() {
        const { getFieldDecorator } = this.props.form;
        const formItemLayout = {
          labelCol: { span: 6 },
          wrapperCol: { span: 14 },
        };
        return (
          <Form {...formItemLayout} onSubmit={this.handleSubmit}>
            <Form.Item label="Plain Text">
              <span className="ant-form-text">China</span>
            </Form.Item>
            <Form.Item label="Select" hasFeedback>
              {getFieldDecorator('select', {
                rules: [{ required: true, message: 'Please select your country!' }],
              })(
                <Select placeholder="Please select a country">
                  <Option value="china">China</Option>
                  <Option value="usa">U.S.A</Option>
                </Select>,
              )}
            </Form.Item>
    
            <Form.Item label="Select[multiple]">
              {getFieldDecorator('select-multiple', {
                rules: [
                  { required: true, message: 'Please select your favourite colors!', type: 'array' },
                ],
              })(
                <Select mode="multiple" placeholder="Please select favourite colors">
                  <Option value="red">Red</Option>
                  <Option value="green">Green</Option>
                  <Option value="blue">Blue</Option>
                </Select>,
              )}
            </Form.Item>
    
            <Form.Item label="InputNumber">
              {getFieldDecorator('input-number', { initialValue: 3 })(<InputNumber min={1} max={10} />)}
              <span className="ant-form-text"> machines</span>
            </Form.Item>
    
            <Form.Item label="Switch">
              {getFieldDecorator('switch', { valuePropName: 'checked' })(<Switch />)}
            </Form.Item>
    
            <Form.Item label="Slider">
              {getFieldDecorator('slider')(
                <Slider
                  marks={{
                    0: 'A',
                    20: 'B',
                    40: 'C',
                    60: 'D',
                    80: 'E',
                    100: 'F',
                  }}
                />,
              )}
            </Form.Item>
    
            <Form.Item label="Radio.Group">
              {getFieldDecorator('radio-group')(
                <Radio.Group>
                  <Radio value="a">item 1</Radio>
                  <Radio value="b">item 2</Radio>
                  <Radio value="c">item 3</Radio>
                </Radio.Group>,
              )}
            </Form.Item>
    
            <Form.Item label="Radio.Button">
              {getFieldDecorator('radio-button')(
                <Radio.Group>
                  <Radio.Button value="a">item 1</Radio.Button>
                  <Radio.Button value="b">item 2</Radio.Button>
                  <Radio.Button value="c">item 3</Radio.Button>
                </Radio.Group>,
              )}
            </Form.Item>
    
            <Form.Item label="Checkbox.Group">
              {getFieldDecorator('checkbox-group', {
                initialValue: ['A', 'B'],
              })(
                <Checkbox.Group style={{ width: '100%' }}>
                  <Row>
                    <Col span={8}>
                      <Checkbox value="A">A</Checkbox>
                    </Col>
                    <Col span={8}>
                      <Checkbox disabled value="B">
                        B
                      </Checkbox>
                    </Col>
                    <Col span={8}>
                      <Checkbox value="C">C</Checkbox>
                    </Col>
                    <Col span={8}>
                      <Checkbox value="D">D</Checkbox>
                    </Col>
                    <Col span={8}>
                      <Checkbox value="E">E</Checkbox>
                    </Col>
                  </Row>
                </Checkbox.Group>,
              )}
            </Form.Item>
    
            <Form.Item label="Rate">
              {getFieldDecorator('rate', {
                initialValue: 3.5,
              })(<Rate />)}
            </Form.Item>
    
            <Form.Item label="Upload" extra="longgggggggggggggggggggggggggggggggggg">
              {getFieldDecorator('upload', {
                valuePropName: 'fileList',
                getValueFromEvent: this.normFile,
              })(
                <Upload name="logo" action="/upload.do" listType="picture">
                  <Button>
                    <Icon type="upload" /> Click to upload
                  </Button>
                </Upload>,
              )}
            </Form.Item>
    
            <Form.Item label="Dragger">
              <div className="dropbox">
                {getFieldDecorator('dragger', {
                  valuePropName: 'fileList',
                  getValueFromEvent: this.normFile,
                })(
                  <Upload.Dragger name="files" action="/upload.do">
                    <p className="ant-upload-drag-icon">
                      <Icon type="inbox" />
                    </p>
                    <p className="ant-upload-text">Click or drag file to this area to upload</p>
                    <p className="ant-upload-hint">Support for a single or bulk upload.</p>
                  </Upload.Dragger>,
                )}
              </div>
            </Form.Item>
    
            <Form.Item wrapperCol={{ span: 12, offset: 6 }}>
              <Button type="primary" htmlType="submit">
                Submit
              </Button>
            </Form.Item>
          </Form>
        );
      }
    }
    
    const WrappedDemo = Form.create({ name: 'validate_other' })(Demo);
    
    ReactDOM.render(<WrappedDemo />, mountNode);
    #components-form-demo-validate-other .dropbox {
      height: 180px;
      line-height: 1.5;
    }

    更多示例参考

    Form.create(options)

    使用方式如下:

    class CustomizedForm extends React.Component {}
    
    CustomizedForm = Form.create({})(CustomizedForm);

    options 的配置项如下。

    参数说明类型
    mapPropsToFields把父组件的属性映射到表单项上(如:把 Redux store 中的值读出),需要对返回值中的表单域数据用 标记,注意表单项将变成受控组件, error 等也需要一并手动传入(props) => ({ [fieldName]: FormField { value } })
    name设置表单域内字段 id 的前缀-
    validateMessages默认校验信息,可用于把默认错误信息改为中文等,格式与 newMessages 返回值一致Object { [nested.path]: String }
    onFieldsChangeForm.Item 子节点的值(包括 error)发生改变时触发,可以把对应的值转存到 Redux storeFunction(props, changedFields, allFields)
    onValuesChange任一表单域的值发生改变时的回调(props, changedValues, allValues) => void

    经过 Form.create 之后如果要拿到 ref,可以使用 rc-form 提供的 wrappedComponentRef,。

    class CustomizedForm extends React.Component { ... }
    
    // use wrappedComponentRef
    const EnhancedForm =  Form.create()(CustomizedForm);
    <EnhancedForm wrappedComponentRef={(form) => this.form = form} />
    this.form // => The instance of CustomizedForm

    经过 Form.create 包装的组件将会自带 this.props.form 属性,this.props.form 提供的 API 如下:

    注意:使用 getFieldsValue getFieldValue setFieldsValue 等时,应确保对应的 field 已经用 getFieldDecorator 注册过了。

    const {
      form: { validateFields },
    } = this.props;
    validateFields((errors, values) => {
      // ...
    });
    validateFields(['field1', 'field2'], (errors, values) => {
      // ...
    });
    validateFields(['field1', 'field2'], options, (errors, values) => {
      // ...
    });
    参数说明类型默认值
    options.first若为 true,则每一表单域的都会在碰到第一个失败了的校验规则后停止校验booleanfalse
    options.firstFields指定表单域会在碰到第一个失败了的校验规则后停止校验String[][]
    options.force对已经校验过的表单域,在 validateTrigger 再次被触发时是否再次校验booleanfalse
    options.scroll定义 validateFieldsAndScroll 的滚动行为,详细配置见 dom-scroll-into-view configObject{}

    validateFields 的 callback 参数示例

    • errors:
    {
      "username": {
        "errors": [
          {
            "message": "Please input your username!",
            "field": "username"
          }
        ]
      },
      "password": {
        "errors": [
          {
            "message": "Please input your Password!",
            "field": "password"
          }
        ]
      }
    }
    • values:
    {
      "username": "username",
      "password": "password",
    }

    Form.createFormField

    用于标记 mapPropsToFields 返回的表单域数据,。

    经过 getFieldDecorator 包装的控件,表单控件会自动添加 value(或 valuePropName 指定的其他属性) onChange(或 trigger 指定的其他属性),数据同步将被 Form 接管,这会导致以下结果:

    • 不再需要也不应该onChange 来做同步,但还是可以继续监听 onChange 等事件。

    • 你不能用控件的 value defaultValue 等属性来设置表单域的值,默认值可以用 getFieldDecorator 里的 initialValue

    • 你不应该用 setState,可以使用 this.props.form.setFieldsValue 来动态改变表单值。

    特别注意

    如果使用的是 react@<15.3.0,则 getFieldDecorator 调用不能位于纯函数组件中:

    getFieldDecorator(id, options) 参数

    更多参数请查看 。

    Form.Item

    注意:一个 Form.Item 建议只放一个被 getFieldDecorator 装饰过的 child,当有多个被装饰过的 child 时,help required validateStatus 无法自动生成。

    参数说明类型默认值版本
    colon配合 label 属性使用,表示是否显示 label 后面的冒号booleantrue
    extra额外的提示信息,和 help 类似,当需要错误信息和提示文案同时出现时,可以使用这个。string|ReactNode
    hasFeedback配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用booleanfalse
    help提示信息,如不设置,则会根据校验规则自动生成string|ReactNode
    htmlFor设置子元素 label htmlFor 属性string3.17.0
    labellabel 标签的文本string|ReactNode
    labelCollabel 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12}sm: {span: 3, offset: 12}。在 3.14.0 之后,你可以通过 Form 的 labelCol 进行统一设置。当和 Form 同时设置时,以 FormItem 为准。
    required是否必填,如不设置,则会根据校验规则自动生成booleanfalse
    validateStatus校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating'string
    wrapperCol需要为输入控件设置布局样式时,使用该属性,用法同 labelCol。在 3.14.0 之后,你可以通过 Form 的 labelCol 进行统一设置。当和 Form 同时设置时,以 FormItem 为准。object

    在 TypeScript 中使用

    import { Form } from 'antd';
    import { FormComponentProps } from 'antd/lib/form';
    
    interface UserFormProps extends FormComponentProps {
      age: number;
      name: string;
    }
    
    class UserForm extends React.Component<UserFormProps, any> {
      // ...
    }
    
    const App = Form.create<UserFormProps>({
      // ...
    })(UserForm);