Form表单

高性能表单控件,自带数据域管理。包含数据录入、校验以及对应样式。

何时使用

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

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

代码演示

Form表单 - 图1

基本使用

基本的表单数据域控制展示,包含布局、初始化、验证、提交。

Form表单 - 图2Form表单 - 图3

TypeScript

JavaScript

  1. import { Form, Input, Button, Checkbox } from 'antd';
  2. const layout = {
  3. labelCol: { span: 8 },
  4. wrapperCol: { span: 16 },
  5. };
  6. const tailLayout = {
  7. wrapperCol: { offset: 8, span: 16 },
  8. };
  9. const Demo = () => {
  10. const onFinish = values => {
  11. console.log('Success:', values);
  12. };
  13. const onFinishFailed = errorInfo => {
  14. console.log('Failed:', errorInfo);
  15. };
  16. return (
  17. <Form
  18. {...layout}
  19. name="basic"
  20. initialValues={{ remember: true }}
  21. onFinish={onFinish}
  22. onFinishFailed={onFinishFailed}
  23. >
  24. <Form.Item
  25. label="Username"
  26. name="username"
  27. rules={[{ required: true, message: 'Please input your username!' }]}
  28. >
  29. <Input />
  30. </Form.Item>
  31. <Form.Item
  32. label="Password"
  33. name="password"
  34. rules={[{ required: true, message: 'Please input your password!' }]}
  35. >
  36. <Input.Password />
  37. </Form.Item>
  38. <Form.Item {...tailLayout} name="remember" valuePropName="checked">
  39. <Checkbox>Remember me</Checkbox>
  40. </Form.Item>
  41. <Form.Item {...tailLayout}>
  42. <Button type="primary" htmlType="submit">
  43. Submit
  44. </Button>
  45. </Form.Item>
  46. </Form>
  47. );
  48. };
  49. ReactDOM.render(<Demo />, mountNode);

Form表单 - 图4

表单方法调用

通过 Form.useForm 对表单数据域进行交互。

Form表单 - 图5Form表单 - 图6

TypeScript

JavaScript

  1. import { Form, Input, Button, Select } from 'antd';
  2. const { Option } = Select;
  3. const layout = {
  4. labelCol: { span: 8 },
  5. wrapperCol: { span: 16 },
  6. };
  7. const tailLayout = {
  8. wrapperCol: { offset: 8, span: 16 },
  9. };
  10. const Demo = () => {
  11. const [form] = Form.useForm();
  12. const onGenderChange = value => {
  13. form.setFieldsValue({
  14. note: `Hi, ${value === 'male' ? 'man' : 'lady'}!`,
  15. });
  16. };
  17. const onFinish = values => {
  18. console.log(values);
  19. };
  20. const onReset = () => {
  21. form.resetFields();
  22. };
  23. const onFill = () => {
  24. form.setFieldsValue({
  25. note: 'Hello world!',
  26. gender: 'male',
  27. });
  28. };
  29. return (
  30. <Form {...layout} form={form} name="control-hooks" onFinish={onFinish}>
  31. <Form.Item name="note" label="Note" rules={[{ required: true }]}>
  32. <Input />
  33. </Form.Item>
  34. <Form.Item name="gender" label="Gender" rules={[{ required: true }]}>
  35. <Select
  36. placeholder="Select a option and change input text above"
  37. onChange={onGenderChange}
  38. allowClear
  39. >
  40. <Option value="male">male</Option>
  41. <Option value="female">female</Option>
  42. <Option value="other">other</Option>
  43. </Select>
  44. </Form.Item>
  45. <Form.Item
  46. noStyle
  47. shouldUpdate={(prevValues, currentValues) => prevValues.gender !== currentValues.gender}
  48. >
  49. {({ getFieldValue }) => {
  50. return getFieldValue('gender') === 'other' ? (
  51. <Form.Item name="customizeGender" label="Customize Gender" rules={[{ required: true }]}>
  52. <Input />
  53. </Form.Item>
  54. ) : null;
  55. }}
  56. </Form.Item>
  57. <Form.Item {...tailLayout}>
  58. <Button type="primary" htmlType="submit">
  59. Submit
  60. </Button>
  61. <Button htmlType="button" onClick={onReset}>
  62. Reset
  63. </Button>
  64. <Button type="link" htmlType="button" onClick={onFill}>
  65. Fill form
  66. </Button>
  67. </Form.Item>
  68. </Form>
  69. );
  70. };
  71. ReactDOM.render(<Demo />, mountNode);
  1. #components-form-demo-control-hooks .ant-btn {
  2. margin-right: 8px;
  3. }

Form表单 - 图7

表单方法调用(Class component)

我们推荐使用 Form.useForm 创建表单数据域进行控制。如果是在 class component 下,你也可以通过 ref 获取数据域。

Form表单 - 图8Form表单 - 图9

TypeScript

JavaScript

  1. import { Form, Input, Button, Select } from 'antd';
  2. const { Option } = Select;
  3. const layout = {
  4. labelCol: { span: 8 },
  5. wrapperCol: { span: 16 },
  6. };
  7. const tailLayout = {
  8. wrapperCol: { offset: 8, span: 16 },
  9. };
  10. class Demo extends React.Component {
  11. formRef = React.createRef();
  12. onGenderChange = value => {
  13. this.formRef.current.setFieldsValue({
  14. note: `Hi, ${value === 'male' ? 'man' : 'lady'}!`,
  15. });
  16. };
  17. onFinish = values => {
  18. console.log(values);
  19. };
  20. onReset = () => {
  21. this.formRef.current.resetFields();
  22. };
  23. onFill = () => {
  24. this.formRef.current.setFieldsValue({
  25. note: 'Hello world!',
  26. gender: 'male',
  27. });
  28. };
  29. render() {
  30. return (
  31. <Form {...layout} ref={this.formRef} name="control-ref" onFinish={this.onFinish}>
  32. <Form.Item name="note" label="Note" rules={[{ required: true }]}>
  33. <Input />
  34. </Form.Item>
  35. <Form.Item name="gender" label="Gender" rules={[{ required: true }]}>
  36. <Select
  37. placeholder="Select a option and change input text above"
  38. onChange={this.onGenderChange}
  39. allowClear
  40. >
  41. <Option value="male">male</Option>
  42. <Option value="female">female</Option>
  43. <Option value="other">other</Option>
  44. </Select>
  45. </Form.Item>
  46. <Form.Item
  47. noStyle
  48. shouldUpdate={(prevValues, currentValues) => prevValues.gender !== currentValues.gender}
  49. >
  50. {({ getFieldValue }) => {
  51. return getFieldValue('gender') === 'other' ? (
  52. <Form.Item
  53. name="customizeGender"
  54. label="Customize Gender"
  55. rules={[{ required: true }]}
  56. >
  57. <Input />
  58. </Form.Item>
  59. ) : null;
  60. }}
  61. </Form.Item>
  62. <Form.Item {...tailLayout}>
  63. <Button type="primary" htmlType="submit">
  64. Submit
  65. </Button>
  66. <Button htmlType="button" onClick={this.onReset}>
  67. Reset
  68. </Button>
  69. <Button type="link" htmlType="button" onClick={this.onFill}>
  70. Fill form
  71. </Button>
  72. </Form.Item>
  73. </Form>
  74. );
  75. }
  76. }
  77. ReactDOM.render(<Demo />, mountNode);
  1. #components-form-demo-control-ref .ant-btn {
  2. margin-right: 8px;
  3. }

Form表单 - 图10

表单布局

表单有三种布局。

Form表单 - 图11Form表单 - 图12

TypeScript

JavaScript

  1. import React, { useState } from 'react';
  2. import { Form, Input, Button, Radio } from 'antd';
  3. const FormLayoutDemo = () => {
  4. const [form] = Form.useForm();
  5. const [formLayout, setFormLayout] = useState('horizontal');
  6. const onFormLayoutChange = ({ layout }) => {
  7. setFormLayout(layout);
  8. };
  9. const formItemLayout =
  10. formLayout === 'horizontal'
  11. ? {
  12. labelCol: { span: 4 },
  13. wrapperCol: { span: 14 },
  14. }
  15. : null;
  16. const buttonItemLayout =
  17. formLayout === 'horizontal'
  18. ? {
  19. wrapperCol: { span: 14, offset: 4 },
  20. }
  21. : null;
  22. return (
  23. <div>
  24. <Form
  25. {...formItemLayout}
  26. layout={formLayout}
  27. form={form}
  28. initialValues={{ layout: formLayout }}
  29. onValuesChange={onFormLayoutChange}
  30. >
  31. <Form.Item label="Form Layout" name="layout">
  32. <Radio.Group value={formLayout}>
  33. <Radio.Button value="horizontal">Horizontal</Radio.Button>
  34. <Radio.Button value="vertical">Vertical</Radio.Button>
  35. <Radio.Button value="inline">Inline</Radio.Button>
  36. </Radio.Group>
  37. </Form.Item>
  38. <Form.Item label="Field A">
  39. <Input placeholder="input placeholder" />
  40. </Form.Item>
  41. <Form.Item label="Field B">
  42. <Input placeholder="input placeholder" />
  43. </Form.Item>
  44. <Form.Item {...buttonItemLayout}>
  45. <Button type="primary">Submit</Button>
  46. </Form.Item>
  47. </Form>
  48. </div>
  49. );
  50. };
  51. ReactDOM.render(<FormLayoutDemo />, mountNode);

Form表单 - 图13

表单尺寸

设置表单组件尺寸,仅对 antd 组件有效。

Form表单 - 图14Form表单 - 图15

TypeScript

JavaScript

  1. import React, { useState } from 'react';
  2. import {
  3. Form,
  4. Input,
  5. Button,
  6. Radio,
  7. Select,
  8. Cascader,
  9. DatePicker,
  10. InputNumber,
  11. TreeSelect,
  12. Switch,
  13. } from 'antd';
  14. const FormSizeDemo = () => {
  15. const [componentSize, setComponentSize] = useState('small');
  16. const onFormLayoutChange = ({ size }) => {
  17. setComponentSize(size);
  18. };
  19. return (
  20. <div>
  21. <Form
  22. labelCol={{ span: 4 }}
  23. wrapperCol={{ span: 14 }}
  24. layout="horizontal"
  25. initialValues={{ size: componentSize }}
  26. onValuesChange={onFormLayoutChange}
  27. size={componentSize}
  28. >
  29. <Form.Item label="Form Size" name="size">
  30. <Radio.Group>
  31. <Radio.Button value="small">Small</Radio.Button>
  32. <Radio.Button value="middle">Middle</Radio.Button>
  33. <Radio.Button value="large">Large</Radio.Button>
  34. </Radio.Group>
  35. </Form.Item>
  36. <Form.Item label="Input">
  37. <Input />
  38. </Form.Item>
  39. <Form.Item label="Select">
  40. <Select>
  41. <Select.Option value="demo">Demo</Select.Option>
  42. </Select>
  43. </Form.Item>
  44. <Form.Item label="TreeSelect">
  45. <TreeSelect
  46. treeData={[
  47. { title: 'Light', value: 'light', children: [{ title: 'Bamboo', value: 'bamboo' }] },
  48. ]}
  49. />
  50. </Form.Item>
  51. <Form.Item label="Cascader">
  52. <Cascader
  53. options={[
  54. {
  55. value: 'zhejiang',
  56. label: 'Zhejiang',
  57. children: [
  58. {
  59. value: 'hangzhou',
  60. label: 'Hangzhou',
  61. },
  62. ],
  63. },
  64. ]}
  65. />
  66. </Form.Item>
  67. <Form.Item label="DatePicker">
  68. <DatePicker />
  69. </Form.Item>
  70. <Form.Item label="InputNumber">
  71. <InputNumber />
  72. </Form.Item>
  73. <Form.Item label="Switch">
  74. <Switch />
  75. </Form.Item>
  76. <Form.Item label="Button">
  77. <Button>Button</Button>
  78. </Form.Item>
  79. </Form>
  80. </div>
  81. );
  82. };
  83. ReactDOM.render(<FormSizeDemo />, mountNode);

Form表单 - 图16

动态增减表单项

动态增加、减少表单项。

  1. import { Form, Input, Button } from 'antd';
  2. import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
  3. const formItemLayout = {
  4. labelCol: {
  5. xs: { span: 24 },
  6. sm: { span: 4 },
  7. },
  8. wrapperCol: {
  9. xs: { span: 24 },
  10. sm: { span: 20 },
  11. },
  12. };
  13. const formItemLayoutWithOutLabel = {
  14. wrapperCol: {
  15. xs: { span: 24, offset: 0 },
  16. sm: { span: 20, offset: 4 },
  17. },
  18. };
  19. const DynamicFieldSet = () => {
  20. const onFinish = values => {
  21. console.log('Received values of form:', values);
  22. };
  23. return (
  24. <Form name="dynamic_form_item" {...formItemLayoutWithOutLabel} onFinish={onFinish}>
  25. <Form.List name="names">
  26. {(fields, { add, remove }) => {
  27. return (
  28. <div>
  29. {fields.map((field, index) => (
  30. <Form.Item
  31. {...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
  32. label={index === 0 ? 'Passengers' : ''}
  33. required={false}
  34. key={field.key}
  35. >
  36. <Form.Item
  37. {...field}
  38. validateTrigger={['onChange', 'onBlur']}
  39. rules={[
  40. {
  41. required: true,
  42. whitespace: true,
  43. message: "Please input passenger's name or delete this field.",
  44. },
  45. ]}
  46. noStyle
  47. >
  48. <Input placeholder="passenger name" style={{ width: '60%', marginRight: 8 }} />
  49. </Form.Item>
  50. {fields.length > 1 ? (
  51. <MinusCircleOutlined
  52. className="dynamic-delete-button"
  53. onClick={() => {
  54. remove(field.name);
  55. }}
  56. />
  57. ) : null}
  58. </Form.Item>
  59. ))}
  60. <Form.Item>
  61. <Button
  62. type="dashed"
  63. onClick={() => {
  64. add();
  65. }}
  66. style={{ width: '60%' }}
  67. >
  68. <PlusOutlined /> Add field
  69. </Button>
  70. </Form.Item>
  71. </div>
  72. );
  73. }}
  74. </Form.List>
  75. <Form.Item>
  76. <Button type="primary" htmlType="submit">
  77. Submit
  78. </Button>
  79. </Form.Item>
  80. </Form>
  81. );
  82. };
  83. ReactDOM.render(<DynamicFieldSet />, mountNode);
  1. .dynamic-delete-button {
  2. cursor: pointer;
  3. position: relative;
  4. top: 4px;
  5. font-size: 24px;
  6. color: #999;
  7. transition: all 0.3s;
  8. }
  9. .dynamic-delete-button:hover {
  10. color: #777;
  11. }
  12. .dynamic-delete-button[disabled] {
  13. cursor: not-allowed;
  14. opacity: 0.5;
  15. }

Form表单 - 图17

嵌套结构与校验信息

name 属性支持嵌套数据结构。通过 validateMessagesmessage 自定义校验信息模板,模板内容可参考此处

Form表单 - 图18Form表单 - 图19

TypeScript

JavaScript

  1. import { Form, Input, InputNumber, Button } from 'antd';
  2. const layout = {
  3. labelCol: { span: 8 },
  4. wrapperCol: { span: 16 },
  5. };
  6. const validateMessages = {
  7. required: 'This field is required!',
  8. types: {
  9. email: 'Not a validate email!',
  10. number: 'Not a validate number!',
  11. },
  12. number: {
  13. range: 'Must be between ${min} and ${max}',
  14. },
  15. };
  16. const Demo = () => {
  17. const onFinish = values => {
  18. console.log(values);
  19. };
  20. return (
  21. <Form {...layout} name="nest-messages" onFinish={onFinish} validateMessages={validateMessages}>
  22. <Form.Item name={['user', 'name']} label="Name" rules={[{ required: true }]}>
  23. <Input />
  24. </Form.Item>
  25. <Form.Item name={['user', 'email']} label="Email" rules={[{ type: 'email' }]}>
  26. <Input />
  27. </Form.Item>
  28. <Form.Item name={['user', 'age']} label="Age" rules={[{ type: 'number', min: 0, max: 99 }]}>
  29. <InputNumber />
  30. </Form.Item>
  31. <Form.Item name={['user', 'website']} label="Website">
  32. <Input />
  33. </Form.Item>
  34. <Form.Item name={['user', 'introduction']} label="Introduction">
  35. <Input.TextArea />
  36. </Form.Item>
  37. <Form.Item wrapperCol={{ ...layout.wrapperCol, offset: 8 }}>
  38. <Button type="primary" htmlType="submit">
  39. Submit
  40. </Button>
  41. </Form.Item>
  42. </Form>
  43. );
  44. };
  45. ReactDOM.render(<Demo />, mountNode);

Form表单 - 图20

复杂一点的控件

这里演示 Form.Item 内有多个元素的使用方式。<Form.Item name="field" /> 只会对它的直接子元素绑定表单功能,例如直接包裹了 Input/Select。如果控件前后还有一些文案或样式装点,或者一个表单项内有多个控件,你可以使用内嵌的 Form.Item 完成。你可以给 Form.Item 自定义 style 进行内联布局,或者添加 noStyle 作为纯粹的无样式绑定组件(类似 3.x 中的 getFieldDecorator)。

  1. - <Form.Item label="Field" name="field">
  2. - <Input />
  3. - </Form.Item>
  4. + <Form.Item label="Field">
  5. + <Form.Item name="field" noStyle><Input /></Form.Item> // 直接包裹才会绑定表单
  6. + <span>description</span>
  7. + </Form.Item>

这里展示了三种典型场景:

  • Username:输入框后面有描述文案或其他组件,在 Form.Item 内使用 <Form.Item name="field" noStyle /> 去绑定对应子控件。

  • Address:有两个控件,在 Form.Item 内使用两个 <Form.Item name="field" noStyle /> 分别绑定对应控件。

  • BirthDate:有两个内联控件,错误信息展示各自控件下,使用两个 <Form.Item name="field" /> 分别绑定对应控件,并修改 style 使其内联布局。

注意,在 label 对应的 Form.Item 上不要在指定 name 属性,这个 Item 只作为布局作用。

更复杂的封装复用方式可以参考下面的 自定义表单控件 演示。

  1. import { Form, Input, Select, Tooltip, Button } from 'antd';
  2. const { Option } = Select;
  3. const Demo = () => {
  4. const onFinish = values => {
  5. console.log('Received values of form: ', values);
  6. };
  7. return (
  8. <Form name="complex-form" onFinish={onFinish} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }}>
  9. <Form.Item label="Username">
  10. <Form.Item
  11. name="username"
  12. noStyle
  13. rules={[{ required: true, message: 'Username is required' }]}
  14. >
  15. <Input style={{ width: 160 }} placeholder="Please input" />
  16. </Form.Item>
  17. <Tooltip title="Useful information">
  18. <a href="#API" style={{ marginLeft: 8 }}>
  19. Need Help?
  20. </a>
  21. </Tooltip>
  22. </Form.Item>
  23. <Form.Item label="Address">
  24. <Input.Group compact>
  25. <Form.Item
  26. name={['address', 'province']}
  27. noStyle
  28. rules={[{ required: true, message: 'Province is required' }]}
  29. >
  30. <Select placeholder="Select province">
  31. <Option value="Zhejiang">Zhejiang</Option>
  32. <Option value="Jiangsu">Jiangsu</Option>
  33. </Select>
  34. </Form.Item>
  35. <Form.Item
  36. name={['address', 'street']}
  37. noStyle
  38. rules={[{ required: true, message: 'Street is required' }]}
  39. >
  40. <Input style={{ width: '50%' }} placeholder="Input street" />
  41. </Form.Item>
  42. </Input.Group>
  43. </Form.Item>
  44. <Form.Item label="BirthDate" style={{ marginBottom: 0 }}>
  45. <Form.Item
  46. name="year"
  47. rules={[{ required: true }]}
  48. style={{ display: 'inline-block', width: 'calc(50% - 5px)', marginRight: 8 }}
  49. >
  50. <Input placeholder="Input birth year" />
  51. </Form.Item>
  52. <Form.Item
  53. name="month"
  54. rules={[{ required: true }]}
  55. style={{ display: 'inline-block', width: 'calc(50% - 5px)' }}
  56. >
  57. <Input placeholder="Input birth month" />
  58. </Form.Item>
  59. </Form.Item>
  60. <Form.Item label=" " colon={false}>
  61. <Button type="primary" htmlType="submit">
  62. Submit
  63. </Button>
  64. </Form.Item>
  65. </Form>
  66. );
  67. };
  68. ReactDOM.render(<Demo />, mountNode);

Form表单 - 图21

自定义表单控件

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

  • 提供受控属性 value 或其它与 valuePropName 的值同名的属性。

  • 提供 onChange 事件或 trigger 的值同名的事件。

Form表单 - 图22Form表单 - 图23

TypeScript

JavaScript

  1. import React, { useState } from 'react';
  2. import { Form, Input, Select, Button } from 'antd';
  3. const { Option } = Select;
  4. interface PriceValue {
  5. number?: number;
  6. currency?: 'rmb' | 'dollar';
  7. }
  8. interface PriceInputProps {
  9. value?: PriceValue;
  10. onChange?: (value: PriceValue) => void;
  11. }
  12. const PriceInput: React.FC<PriceInputProps> = ({ value = {}, onChange }) => {
  13. const [number, setNumber] = useState(0);
  14. const [currency, setCurrency] = useState('rmb');
  15. const triggerChange = changedValue => {
  16. if (onChange) {
  17. onChange({ number, currency, ...value, ...changedValue });
  18. }
  19. };
  20. const onNumberChange = e => {
  21. const newNumber = parseInt(e.target.value || 0, 10);
  22. if (Number.isNaN(number)) {
  23. return;
  24. }
  25. if (!('number' in value)) {
  26. setNumber(newNumber);
  27. }
  28. triggerChange({ number: newNumber });
  29. };
  30. const onCurrencyChange = newCurrency => {
  31. if (!('currency' in value)) {
  32. setCurrency(newCurrency);
  33. }
  34. triggerChange({ currency: newCurrency });
  35. };
  36. return (
  37. <span>
  38. <Input
  39. type="text"
  40. value={value.number || number}
  41. onChange={onNumberChange}
  42. style={{ width: 100, marginRight: 8 }}
  43. />
  44. <Select value={value.currency || currency} style={{ width: 80 }} onChange={onCurrencyChange}>
  45. <Option value="rmb">RMB</Option>
  46. <Option value="dollar">Dollar</Option>
  47. </Select>
  48. </span>
  49. );
  50. };
  51. const Demo = () => {
  52. const onFinish = values => {
  53. console.log('Received values from form: ', values);
  54. };
  55. const checkPrice = (rule, value) => {
  56. if (value.number > 0) {
  57. return Promise.resolve();
  58. }
  59. return Promise.reject('Price must be greater than zero!');
  60. };
  61. return (
  62. <Form
  63. name="customized_form_controls"
  64. layout="inline"
  65. onFinish={onFinish}
  66. initialValues={{
  67. price: {
  68. number: 0,
  69. currency: 'rmb',
  70. },
  71. }}
  72. >
  73. <Form.Item name="price" label="Price" rules={[{ validator: checkPrice }]}>
  74. <PriceInput />
  75. </Form.Item>
  76. <Form.Item>
  77. <Button type="primary" htmlType="submit">
  78. Submit
  79. </Button>
  80. </Form.Item>
  81. </Form>
  82. );
  83. };
  84. ReactDOM.render(<Demo />, mountNode);

Form表单 - 图24

表单数据存储于上层组件

通过 onFieldsChangefields,可以把表单的数据存储到上层组件或者 Reduxdva 中,更多可参考 rc-field-form 示例

注意: 将表单数据存储于外部容器并非好的实践,如无必要请避免使用。

Form表单 - 图25Form表单 - 图26

TypeScript

JavaScript

  1. import React, { useState } from 'react';
  2. import { Form, Input } from 'antd';
  3. interface FieldData {
  4. name: string[];
  5. value: any;
  6. touched: boolean;
  7. validating: boolean;
  8. errors: string[];
  9. }
  10. interface CustomizedFormProps {
  11. onChange: (fields: FieldData[]) => void;
  12. fields: FieldData[];
  13. }
  14. const CustomizedForm: React.FC<CustomizedFormProps> = ({ onChange, fields }) => {
  15. return (
  16. <Form
  17. name="global_state"
  18. layout="inline"
  19. fields={fields}
  20. onFieldsChange={(changedFields, allFields) => {
  21. onChange(allFields);
  22. }}
  23. >
  24. <Form.Item
  25. name="username"
  26. label="Username"
  27. rules={[{ required: true, message: 'Username is required!' }]}
  28. >
  29. <Input />
  30. </Form.Item>
  31. </Form>
  32. );
  33. };
  34. const Demo = () => {
  35. const [fields, setFields] = useState([{ name: ['username'], value: 'Ant Design' }]);
  36. return (
  37. <div>
  38. <CustomizedForm
  39. fields={fields}
  40. onChange={newFields => {
  41. setFields(newFields);
  42. }}
  43. />
  44. <pre className="language-bash">{JSON.stringify(fields, null, 2)}</pre>
  45. </div>
  46. );
  47. };
  48. ReactDOM.render(<Demo />, mountNode);

Form表单 - 图27

多表单联动

通过 Form.Provider 在表单间处理数据。本例子中,Modal 的确认按钮在 Form 之外,通过 form.submit 方法调用表单提交功能。反之,则推荐使用 <Button htmlType="submit" /> 调用 web 原生提交逻辑。

Form表单 - 图28Form表单 - 图29

TypeScript

JavaScript

  1. import React, { useState, useEffect, useRef } from 'react';
  2. import { Form, Input, InputNumber, Modal, Button, Avatar, Typography } from 'antd';
  3. import { SmileOutlined, UserOutlined } from '@ant-design/icons';
  4. const layout = {
  5. labelCol: { span: 8 },
  6. wrapperCol: { span: 16 },
  7. };
  8. const tailLayout = {
  9. wrapperCol: { offset: 8, span: 16 },
  10. };
  11. interface ModalFormProps {
  12. visible: boolean;
  13. onCancel: () => void;
  14. }
  15. const ModalForm: React.FC<ModalFormProps> = ({ visible, onCancel }) => {
  16. const [form] = Form.useForm();
  17. const prevVisibleRef = useRef();
  18. useEffect(() => {
  19. prevVisibleRef.current = visible;
  20. }, [visible]);
  21. const prevVisible = prevVisibleRef.current;
  22. useEffect(() => {
  23. console.log(visible, prevVisible);
  24. if (!visible && prevVisible) {
  25. form.resetFields();
  26. }
  27. }, [visible]);
  28. const onOk = () => {
  29. form.submit();
  30. };
  31. return (
  32. <Modal title="Basic Drawer" visible={visible} onOk={onOk} onCancel={onCancel}>
  33. <Form form={form} layout="vertical" name="userForm">
  34. <Form.Item name="name" label="User Name" rules={[{ required: true }]}>
  35. <Input />
  36. </Form.Item>
  37. <Form.Item name="age" label="User Age" rules={[{ required: true }]}>
  38. <InputNumber />
  39. </Form.Item>
  40. </Form>
  41. </Modal>
  42. );
  43. };
  44. const Demo = () => {
  45. const [visible, setVisible] = useState(false);
  46. const showUserModal = () => {
  47. setVisible(true);
  48. };
  49. const hideUserModal = () => {
  50. setVisible(false);
  51. };
  52. const onFinish = values => {
  53. console.log('Finish:', values);
  54. };
  55. return (
  56. <div>
  57. <Form.Provider
  58. onFormFinish={(name, { values, forms }) => {
  59. if (name === 'userForm') {
  60. const { basicForm } = forms;
  61. const users = basicForm.getFieldValue('users') || [];
  62. basicForm.setFieldsValue({ users: [...users, values] });
  63. setVisible(false);
  64. }
  65. }}
  66. >
  67. <Form {...layout} name="basicForm" onFinish={onFinish}>
  68. <Form.Item name="group" label="Group Name" rules={[{ required: true }]}>
  69. <Input />
  70. </Form.Item>
  71. <Form.Item
  72. label="User List"
  73. shouldUpdate={(prevValues, curValues) => prevValues.users !== curValues.users}
  74. >
  75. {({ getFieldValue }) => {
  76. const users = getFieldValue('users') || [];
  77. return users.length ? (
  78. <ul>
  79. {users.map((user, index) => (
  80. <li key={index} className="user">
  81. <Avatar icon={<UserOutlined />} />
  82. {user.name} - {user.age}
  83. </li>
  84. ))}
  85. </ul>
  86. ) : (
  87. <Typography.Text className="ant-form-text" type="secondary">
  88. ( <SmileOutlined /> No user yet. )
  89. </Typography.Text>
  90. );
  91. }}
  92. </Form.Item>
  93. <Form.Item {...tailLayout}>
  94. <Button htmlType="submit" type="primary">
  95. Submit
  96. </Button>
  97. <Button htmlType="button" style={{ marginLeft: 8 }} onClick={showUserModal}>
  98. Add User
  99. </Button>
  100. </Form.Item>
  101. </Form>
  102. <ModalForm visible={visible} onCancel={hideUserModal} />
  103. </Form.Provider>
  104. </div>
  105. );
  106. };
  107. ReactDOM.render(<Demo />, mountNode);
  1. #components-form-demo-form-context .user {
  2. margin-bottom: 8px;
  3. }
  4. #components-form-demo-form-context .user .ant-avatar {
  5. margin-right: 8px;
  6. }

Form表单 - 图30

内联登录栏

内联登录栏,常用在顶部导航栏中。

Form表单 - 图31Form表单 - 图32

TypeScript

JavaScript

  1. import React, { useState, useEffect } from 'react';
  2. import { Form, Input, Button } from 'antd';
  3. import { UserOutlined, LockOutlined } from '@ant-design/icons';
  4. const HorizontalLoginForm = () => {
  5. const [form] = Form.useForm();
  6. const [, forceUpdate] = useState();
  7. // To disable submit button at the beginning.
  8. useEffect(() => {
  9. forceUpdate({});
  10. }, []);
  11. const onFinish = values => {
  12. console.log('Finish:', values);
  13. };
  14. return (
  15. <Form form={form} name="horizontal_login" layout="inline" onFinish={onFinish}>
  16. <Form.Item
  17. name="username"
  18. rules={[{ required: true, message: 'Please input your username!' }]}
  19. >
  20. <Input prefix={<UserOutlined className="site-form-item-icon" />} placeholder="Username" />
  21. </Form.Item>
  22. <Form.Item
  23. name="password"
  24. rules={[{ required: true, message: 'Please input your password!' }]}
  25. >
  26. <Input
  27. prefix={<LockOutlined className="site-form-item-icon" />}
  28. type="password"
  29. placeholder="Password"
  30. />
  31. </Form.Item>
  32. <Form.Item shouldUpdate={true}>
  33. {() => (
  34. <Button
  35. type="primary"
  36. htmlType="submit"
  37. disabled={
  38. !form.isFieldsTouched(true) ||
  39. form.getFieldsError().filter(({ errors }) => errors.length).length
  40. }
  41. >
  42. Log in
  43. </Button>
  44. )}
  45. </Form.Item>
  46. </Form>
  47. );
  48. };
  49. ReactDOM.render(<HorizontalLoginForm />, mountNode);

Form表单 - 图33

登录框

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

Form表单 - 图34Form表单 - 图35

TypeScript

JavaScript

  1. import { Form, Input, Button, Checkbox } from 'antd';
  2. import { UserOutlined, LockOutlined } from '@ant-design/icons';
  3. const NormalLoginForm = () => {
  4. const onFinish = values => {
  5. console.log('Received values of form: ', values);
  6. };
  7. return (
  8. <Form
  9. name="normal_login"
  10. className="login-form"
  11. initialValues={{ remember: true }}
  12. onFinish={onFinish}
  13. >
  14. <Form.Item
  15. name="username"
  16. rules={[{ required: true, message: 'Please input your Username!' }]}
  17. >
  18. <Input prefix={<UserOutlined className="site-form-item-icon" />} placeholder="Username" />
  19. </Form.Item>
  20. <Form.Item
  21. name="password"
  22. rules={[{ required: true, message: 'Please input your Password!' }]}
  23. >
  24. <Input
  25. prefix={<LockOutlined className="site-form-item-icon" />}
  26. type="password"
  27. placeholder="Password"
  28. />
  29. </Form.Item>
  30. <Form.Item>
  31. <Form.Item name="remember" valuePropName="checked" noStyle>
  32. <Checkbox>Remember me</Checkbox>
  33. </Form.Item>
  34. <a className="login-form-forgot" href="">
  35. Forgot password
  36. </a>
  37. </Form.Item>
  38. <Form.Item>
  39. <Button type="primary" htmlType="submit" className="login-form-button">
  40. Log in
  41. </Button>
  42. Or <a href="">register now!</a>
  43. </Form.Item>
  44. </Form>
  45. );
  46. };
  47. ReactDOM.render(<NormalLoginForm />, 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. }

Form表单 - 图36

注册新用户

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

Form表单 - 图37Form表单 - 图38

TypeScript

JavaScript

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

Form表单 - 图39

高级搜索

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

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

Form表单 - 图40Form表单 - 图41

TypeScript

JavaScript

  1. import React, { useState } from 'react';
  2. import { Form, Row, Col, Input, Button } from 'antd';
  3. import { DownOutlined, UpOutlined } from '@ant-design/icons';
  4. const AdvancedSearchForm = () => {
  5. const [expand, setExpand] = useState(false);
  6. const [form] = Form.useForm();
  7. const getFields = () => {
  8. const count = expand ? 10 : 6;
  9. const children = [];
  10. for (let i = 0; i < count; i++) {
  11. children.push(
  12. <Col span={8} key={i}>
  13. <Form.Item
  14. name={`field-${i}`}
  15. label={`Field ${i}`}
  16. rules={[
  17. {
  18. required: true,
  19. message: 'Input something!',
  20. },
  21. ]}
  22. >
  23. <Input placeholder="placeholder" />
  24. </Form.Item>
  25. </Col>,
  26. );
  27. }
  28. return children;
  29. };
  30. const onFinish = values => {
  31. console.log('Received values of form: ', values);
  32. };
  33. return (
  34. <Form
  35. form={form}
  36. name="advanced_search"
  37. className="ant-advanced-search-form"
  38. onFinish={onFinish}
  39. >
  40. <Row gutter={24}>{getFields()}</Row>
  41. <Row>
  42. <Col span={24} style={{ textAlign: 'right' }}>
  43. <Button type="primary" htmlType="submit">
  44. Search
  45. </Button>
  46. <Button
  47. style={{ marginLeft: 8 }}
  48. onClick={() => {
  49. form.resetFields();
  50. }}
  51. >
  52. Clear
  53. </Button>
  54. <a
  55. style={{ marginLeft: 8, fontSize: 12 }}
  56. onClick={() => {
  57. setExpand(!expand);
  58. }}
  59. >
  60. {expand ? <UpOutlined /> : <DownOutlined />} Collapse
  61. </a>
  62. </Col>
  63. </Row>
  64. </Form>
  65. );
  66. };
  67. ReactDOM.render(
  68. <div>
  69. <AdvancedSearchForm />
  70. <div className="search-result-list">Search Result List</div>
  71. </div>,
  72. mountNode,
  73. );
  1. .ant-advanced-search-form {
  2. padding: 24px;
  3. background: #fbfbfb;
  4. border: 1px solid #d9d9d9;
  5. border-radius: 2px;
  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. }

Form表单 - 图42

弹出层中的新建表单

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

Form表单 - 图43Form表单 - 图44

TypeScript

JavaScript

  1. import React, { useState } from 'react';
  2. import { Button, Modal, Form, Input, Radio } from 'antd';
  3. interface Values {
  4. title: string;
  5. description: string;
  6. modifier: string;
  7. }
  8. interface CollectionCreateFormProps {
  9. visible: boolean;
  10. onCreate: (values: Values) => void;
  11. onCancel: () => void;
  12. }
  13. const CollectionCreateForm: React.FC<CollectionCreateFormProps> = ({
  14. visible,
  15. onCreate,
  16. onCancel,
  17. }) => {
  18. const [form] = Form.useForm();
  19. return (
  20. <Modal
  21. visible={visible}
  22. title="Create a new collection"
  23. okText="Create"
  24. cancelText="Cancel"
  25. onCancel={onCancel}
  26. onOk={() => {
  27. form
  28. .validateFields()
  29. .then(values => {
  30. form.resetFields();
  31. onCreate(values);
  32. })
  33. .catch(info => {
  34. console.log('Validate Failed:', info);
  35. });
  36. }}
  37. >
  38. <Form
  39. form={form}
  40. layout="vertical"
  41. name="form_in_modal"
  42. initialValues={{ modifier: 'public' }}
  43. >
  44. <Form.Item
  45. name="title"
  46. label="Title"
  47. rules={[{ required: true, message: 'Please input the title of collection!' }]}
  48. >
  49. <Input />
  50. </Form.Item>
  51. <Form.Item name="description" label="Description">
  52. <Input type="textarea" />
  53. </Form.Item>
  54. <Form.Item name="modifier" className="collection-create-form_last-form-item">
  55. <Radio.Group>
  56. <Radio value="public">Public</Radio>
  57. <Radio value="private">Private</Radio>
  58. </Radio.Group>
  59. </Form.Item>
  60. </Form>
  61. </Modal>
  62. );
  63. };
  64. const CollectionsPage = () => {
  65. const [visible, setVisible] = useState(false);
  66. const onCreate = values => {
  67. console.log('Received values of form: ', values);
  68. setVisible(false);
  69. };
  70. return (
  71. <div>
  72. <Button
  73. type="primary"
  74. onClick={() => {
  75. setVisible(true);
  76. }}
  77. >
  78. New Collection
  79. </Button>
  80. <CollectionCreateForm
  81. visible={visible}
  82. onCreate={onCreate}
  83. onCancel={() => {
  84. setVisible(false);
  85. }}
  86. />
  87. </div>
  88. );
  89. };
  90. ReactDOM.render(<CollectionsPage />, mountNode);
  1. .collection-create-form_last-form-item {
  2. margin-bottom: 0;
  3. }

Form表单 - 图45

时间类控件

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

Form表单 - 图46Form表单 - 图47

TypeScript

JavaScript

  1. import { Form, DatePicker, TimePicker, Button } from 'antd';
  2. const { MonthPicker, RangePicker } = DatePicker;
  3. const formItemLayout = {
  4. labelCol: {
  5. xs: { span: 24 },
  6. sm: { span: 8 },
  7. },
  8. wrapperCol: {
  9. xs: { span: 24 },
  10. sm: { span: 16 },
  11. },
  12. };
  13. const config = {
  14. rules: [{ type: 'object', required: true, message: 'Please select time!' }],
  15. };
  16. const rangeConfig = {
  17. rules: [{ type: 'array', required: true, message: 'Please select time!' }],
  18. };
  19. const TimeRelatedForm = () => {
  20. const onFinish = fieldsValue => {
  21. // Should format date value before submit.
  22. const rangeValue = fieldsValue['range-picker'];
  23. const rangeTimeValue = fieldsValue['range-time-picker'];
  24. const values = {
  25. ...fieldsValue,
  26. 'date-picker': fieldsValue['date-picker'].format('YYYY-MM-DD'),
  27. 'date-time-picker': fieldsValue['date-time-picker'].format('YYYY-MM-DD HH:mm:ss'),
  28. 'month-picker': fieldsValue['month-picker'].format('YYYY-MM'),
  29. 'range-picker': [rangeValue[0].format('YYYY-MM-DD'), rangeValue[1].format('YYYY-MM-DD')],
  30. 'range-time-picker': [
  31. rangeTimeValue[0].format('YYYY-MM-DD HH:mm:ss'),
  32. rangeTimeValue[1].format('YYYY-MM-DD HH:mm:ss'),
  33. ],
  34. 'time-picker': fieldsValue['time-picker'].format('HH:mm:ss'),
  35. };
  36. console.log('Received values of form: ', values);
  37. };
  38. return (
  39. <Form name="time_related_controls" {...formItemLayout} onFinish={onFinish}>
  40. <Form.Item name="date-picker" label="DatePicker" {...config}>
  41. <DatePicker />
  42. </Form.Item>
  43. <Form.Item name="date-time-picker" label="DatePicker[showTime]" {...config}>
  44. <DatePicker showTime format="YYYY-MM-DD HH:mm:ss" />
  45. </Form.Item>
  46. <Form.Item name="month-picker" label="MonthPicker" {...config}>
  47. <MonthPicker />
  48. </Form.Item>
  49. <Form.Item name="range-picker" label="RangePicker" {...rangeConfig}>
  50. <RangePicker />
  51. </Form.Item>
  52. <Form.Item name="range-time-picker" label="RangePicker[showTime]" {...rangeConfig}>
  53. <RangePicker showTime format="YYYY-MM-DD HH:mm:ss" />
  54. </Form.Item>
  55. <Form.Item name="time-picker" label="TimePicker" {...config}>
  56. <TimePicker />
  57. </Form.Item>
  58. <Form.Item
  59. wrapperCol={{
  60. xs: { span: 24, offset: 0 },
  61. sm: { span: 16, offset: 8 },
  62. }}
  63. >
  64. <Button type="primary" htmlType="submit">
  65. Submit
  66. </Button>
  67. </Form.Item>
  68. </Form>
  69. );
  70. };
  71. ReactDOM.render(<TimeRelatedForm />, mountNode);

Form表单 - 图48

自行处理表单数据

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

Form表单 - 图49Form表单 - 图50

TypeScript

JavaScript

  1. import React, { useState } from 'react';
  2. import { Form, InputNumber } from 'antd';
  3. function validatePrimeNumber(number) {
  4. if (number === 11) {
  5. return {
  6. validateStatus: 'success',
  7. errorMsg: null,
  8. };
  9. }
  10. return {
  11. validateStatus: 'error',
  12. errorMsg: 'The prime between 8 and 12 is 11!',
  13. };
  14. }
  15. const formItemLayout = {
  16. labelCol: { span: 7 },
  17. wrapperCol: { span: 12 },
  18. };
  19. const RawForm = () => {
  20. const [number, setNumber] = useState({
  21. value: 11,
  22. });
  23. const tips =
  24. 'A prime is a natural number greater than 1 that has no positive divisors other than 1 and itself.';
  25. const onNumberChange = value => {
  26. setNumber({
  27. ...validatePrimeNumber(value),
  28. value,
  29. });
  30. };
  31. return (
  32. <Form>
  33. <Form.Item
  34. {...formItemLayout}
  35. label="Prime between 8 & 12"
  36. validateStatus={number.validateStatus}
  37. help={number.errorMsg || tips}
  38. >
  39. <InputNumber min={8} max={12} value={number.value} onChange={onNumberChange} />
  40. </Form.Item>
  41. </Form>
  42. );
  43. };
  44. ReactDOM.render(<RawForm />, mountNode);

Form表单 - 图51

自定义校验

我们提供了 validateStatus help hasFeedback 等属性,你可以不通过 Form 自己定义校验的时机和内容。

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

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

  • help:设置校验文案。

Form表单 - 图52Form表单 - 图53

TypeScript

JavaScript

  1. import { SmileOutlined } from '@ant-design/icons';
  2. import { Form, Input, DatePicker, TimePicker, Select, Cascader, InputNumber } from 'antd';
  3. const { Option } = Select;
  4. const formItemLayout = {
  5. labelCol: {
  6. xs: { span: 24 },
  7. sm: { span: 5 },
  8. },
  9. wrapperCol: {
  10. xs: { span: 24 },
  11. sm: { span: 12 },
  12. },
  13. };
  14. ReactDOM.render(
  15. <Form {...formItemLayout}>
  16. <Form.Item
  17. label="Fail"
  18. validateStatus="error"
  19. help="Should be combination of numbers & alphabets"
  20. >
  21. <Input placeholder="unavailable choice" id="error" />
  22. </Form.Item>
  23. <Form.Item label="Warning" validateStatus="warning">
  24. <Input placeholder="Warning" id="warning" prefix={<SmileOutlined />} />
  25. </Form.Item>
  26. <Form.Item
  27. label="Validating"
  28. hasFeedback
  29. validateStatus="validating"
  30. help="The information is being validated..."
  31. >
  32. <Input placeholder="I'm the content is being validated" id="validating" />
  33. </Form.Item>
  34. <Form.Item label="Success" hasFeedback validateStatus="success">
  35. <Input placeholder="I'm the content" id="success" />
  36. </Form.Item>
  37. <Form.Item label="Warning" hasFeedback validateStatus="warning">
  38. <Input placeholder="Warning" id="warning2" />
  39. </Form.Item>
  40. <Form.Item
  41. label="Fail"
  42. hasFeedback
  43. validateStatus="error"
  44. help="Should be combination of numbers & alphabets"
  45. >
  46. <Input placeholder="unavailable choice" id="error2" />
  47. </Form.Item>
  48. <Form.Item label="Success" hasFeedback validateStatus="success">
  49. <DatePicker style={{ width: '100%' }} />
  50. </Form.Item>
  51. <Form.Item label="Warning" hasFeedback validateStatus="warning">
  52. <TimePicker style={{ width: '100%' }} />
  53. </Form.Item>
  54. <Form.Item label="Error" hasFeedback validateStatus="error">
  55. <Select>
  56. <Option value="1">Option 1</Option>
  57. <Option value="2">Option 2</Option>
  58. <Option value="3">Option 3</Option>
  59. </Select>
  60. </Form.Item>
  61. <Form.Item
  62. label="Validating"
  63. hasFeedback
  64. validateStatus="validating"
  65. help="The information is being validated..."
  66. >
  67. <Cascader options={[]} />
  68. </Form.Item>
  69. <Form.Item label="inline" style={{ marginBottom: 0 }}>
  70. <Form.Item
  71. validateStatus="error"
  72. help="Please select the correct date"
  73. style={{ display: 'inline-block', width: 'calc(50% - 12px)' }}
  74. >
  75. <DatePicker />
  76. </Form.Item>
  77. <span style={{ display: 'inline-block', width: '24px', textAlign: 'center' }}>-</span>
  78. <Form.Item style={{ display: 'inline-block', width: 'calc(50% - 12px)' }}>
  79. <DatePicker />
  80. </Form.Item>
  81. </Form.Item>
  82. <Form.Item label="Success" hasFeedback validateStatus="success">
  83. <InputNumber style={{ width: '100%' }} />
  84. </Form.Item>
  85. <Form.Item label="Success" hasFeedback validateStatus="success">
  86. <Input allowClear placeholder="with allowClear" />
  87. </Form.Item>
  88. <Form.Item label="Warning" hasFeedback validateStatus="warning">
  89. <Input.Password placeholder="with input password" />
  90. </Form.Item>
  91. <Form.Item label="Error" hasFeedback validateStatus="error">
  92. <Input.Password allowClear placeholder="with input password and allowClear" />
  93. </Form.Item>
  94. </Form>,
  95. mountNode,
  96. );

Form表单 - 图54

动态校验规则

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

Form表单 - 图55Form表单 - 图56

TypeScript

JavaScript

  1. import React, { useState, useEffect } from 'react';
  2. import { Form, Input, Button, Checkbox } from 'antd';
  3. const formItemLayout = {
  4. labelCol: { span: 4 },
  5. wrapperCol: { span: 8 },
  6. };
  7. const formTailLayout = {
  8. labelCol: { span: 4 },
  9. wrapperCol: { span: 8, offset: 4 },
  10. };
  11. const DynamicRule = () => {
  12. const [form] = Form.useForm();
  13. const [checkNick, setCheckNick] = useState(false);
  14. useEffect(() => {
  15. form.validateFields(['nickname']);
  16. }, [checkNick]);
  17. const onCheckboxChange = e => {
  18. setCheckNick(e.target.checked);
  19. };
  20. const onCheck = async () => {
  21. try {
  22. const values = await form.validateFields();
  23. console.log('Success:', values);
  24. } catch (errorInfo) {
  25. console.log('Failed:', errorInfo);
  26. }
  27. };
  28. return (
  29. <Form form={form} name="dynamic_rule">
  30. <Form.Item
  31. {...formItemLayout}
  32. name="username"
  33. label="Name"
  34. rules={[
  35. {
  36. required: true,
  37. message: 'Please input your name',
  38. },
  39. ]}
  40. >
  41. <Input placeholder="Please input your name" />
  42. </Form.Item>
  43. <Form.Item
  44. {...formItemLayout}
  45. name="nickname"
  46. label="Nickname"
  47. rules={[
  48. {
  49. required: checkNick,
  50. message: 'Please input your nickname',
  51. },
  52. ]}
  53. >
  54. <Input placeholder="Please input your nickname" />
  55. </Form.Item>
  56. <Form.Item {...formTailLayout}>
  57. <Checkbox checked={checkNick} onChange={onCheckboxChange}>
  58. Nickname is required
  59. </Checkbox>
  60. </Form.Item>
  61. <Form.Item {...formTailLayout}>
  62. <Button type="primary" onClick={onCheck}>
  63. Check
  64. </Button>
  65. </Form.Item>
  66. </Form>
  67. );
  68. };
  69. ReactDOM.render(<DynamicRule />, mountNode);

Form表单 - 图57

校验其他组件

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

Form表单 - 图58Form表单 - 图59

TypeScript

JavaScript

  1. import {
  2. Form,
  3. Select,
  4. InputNumber,
  5. Switch,
  6. Radio,
  7. Slider,
  8. Button,
  9. Upload,
  10. Rate,
  11. Checkbox,
  12. Row,
  13. Col,
  14. } from 'antd';
  15. import { UploadOutlined, InboxOutlined } from '@ant-design/icons';
  16. const { Option } = Select;
  17. const formItemLayout = {
  18. labelCol: { span: 6 },
  19. wrapperCol: { span: 14 },
  20. };
  21. const normFile = e => {
  22. console.log('Upload event:', e);
  23. if (Array.isArray(e)) {
  24. return e;
  25. }
  26. return e && e.fileList;
  27. };
  28. const Demo = () => {
  29. const onFinish = values => {
  30. console.log('Received values of form: ', values);
  31. };
  32. return (
  33. <Form
  34. name="validate_other"
  35. {...formItemLayout}
  36. onFinish={onFinish}
  37. initialValues={{
  38. ['input-number']: 3,
  39. ['checkbox-group']: ['A', 'B'],
  40. rate: 3.5,
  41. }}
  42. >
  43. <Form.Item label="Plain Text">
  44. <span className="ant-form-text">China</span>
  45. </Form.Item>
  46. <Form.Item
  47. name="select"
  48. label="Select"
  49. hasFeedback
  50. rules={[{ required: true, message: 'Please select your country!' }]}
  51. >
  52. <Select placeholder="Please select a country">
  53. <Option value="china">China</Option>
  54. <Option value="usa">U.S.A</Option>
  55. </Select>
  56. </Form.Item>
  57. <Form.Item
  58. name="select-multiple"
  59. label="Select[multiple]"
  60. rules={[{ required: true, message: 'Please select your favourite colors!', type: 'array' }]}
  61. >
  62. <Select mode="multiple" placeholder="Please select favourite colors">
  63. <Option value="red">Red</Option>
  64. <Option value="green">Green</Option>
  65. <Option value="blue">Blue</Option>
  66. </Select>
  67. </Form.Item>
  68. <Form.Item label="InputNumber">
  69. <Form.Item name="input-number" noStyle>
  70. <InputNumber min={1} max={10} />
  71. </Form.Item>
  72. <span className="ant-form-text"> machines</span>
  73. </Form.Item>
  74. <Form.Item name="switch" label="Switch" valuePropName="checked">
  75. <Switch />
  76. </Form.Item>
  77. <Form.Item name="slider" label="Slider">
  78. <Slider
  79. marks={{
  80. 0: 'A',
  81. 20: 'B',
  82. 40: 'C',
  83. 60: 'D',
  84. 80: 'E',
  85. 100: 'F',
  86. }}
  87. />
  88. </Form.Item>
  89. <Form.Item name="radio-group" label="Radio.Group">
  90. <Radio.Group>
  91. <Radio value="a">item 1</Radio>
  92. <Radio value="b">item 2</Radio>
  93. <Radio value="c">item 3</Radio>
  94. </Radio.Group>
  95. </Form.Item>
  96. <Form.Item name="radio-button" label="Radio.Button">
  97. <Radio.Group>
  98. <Radio.Button value="a">item 1</Radio.Button>
  99. <Radio.Button value="b">item 2</Radio.Button>
  100. <Radio.Button value="c">item 3</Radio.Button>
  101. </Radio.Group>
  102. </Form.Item>
  103. <Form.Item name="checkbox-group" label="Checkbox.Group">
  104. <Checkbox.Group style={{ width: '100%' }}>
  105. <Row>
  106. <Col span={8}>
  107. <Checkbox value="A">A</Checkbox>
  108. </Col>
  109. <Col span={8}>
  110. <Checkbox disabled value="B">
  111. B
  112. </Checkbox>
  113. </Col>
  114. <Col span={8}>
  115. <Checkbox value="C">C</Checkbox>
  116. </Col>
  117. <Col span={8}>
  118. <Checkbox value="D">D</Checkbox>
  119. </Col>
  120. <Col span={8}>
  121. <Checkbox value="E">E</Checkbox>
  122. </Col>
  123. </Row>
  124. </Checkbox.Group>
  125. </Form.Item>
  126. <Form.Item name="rate" label="Rate">
  127. <Rate />
  128. </Form.Item>
  129. <Form.Item
  130. name="upload"
  131. label="Upload"
  132. valuePropName="fileList"
  133. getValueFromEvent={normFile}
  134. extra="longgggggggggggggggggggggggggggggggggg"
  135. >
  136. <Upload name="logo" action="/upload.do" listType="picture">
  137. <Button>
  138. <UploadOutlined /> Click to upload
  139. </Button>
  140. </Upload>
  141. </Form.Item>
  142. <Form.Item label="Dragger">
  143. <Form.Item name="dragger" valuePropName="fileList" getValueFromEvent={normFile} noStyle>
  144. <Upload.Dragger name="files" action="/upload.do">
  145. <p className="ant-upload-drag-icon">
  146. <InboxOutlined />
  147. </p>
  148. <p className="ant-upload-text">Click or drag file to this area to upload</p>
  149. <p className="ant-upload-hint">Support for a single or bulk upload.</p>
  150. </Upload.Dragger>
  151. </Form.Item>
  152. </Form.Item>
  153. <Form.Item wrapperCol={{ span: 12, offset: 6 }}>
  154. <Button type="primary" htmlType="submit">
  155. Submit
  156. </Button>
  157. </Form.Item>
  158. </Form>
  159. );
  160. };
  161. ReactDOM.render(<Demo />, mountNode);

API

Form

参数说明类型默认值
component设置 Form 渲染元素,为 false 则不创建 DOM 节点ComponentType | falseform
colon配置 Form.Item 的 colon 的默认值。表示是否显示 label 后面的冒号 (只有在属性 layout 为 horizontal 时有效)booleantrue
fields通过状态管理(如 redux)控制表单字段,如非强需求不推荐使用。查看示例FieldData[]-
formForm.useForm() 创建的 form 控制实例,不提供时会自动创建FormInstance-
hideRequiredMark隐藏所有表单项的必选标记booleanfalse
initialValues表单默认值,只有初始化以及重置时生效object-
labelAlignlabel 标签的文本对齐方式left | rightright
labelCollabel 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12}sm: {span: 3, offset: 12}object-
layout表单布局horizontal | vertical | inlinehorizontal
name表单名称,会作为表单字段 id 前缀使用string-
scrollToFirstError提交失败自动滚动到第一个错误字段false-
size设置字段组件的尺寸(仅限 antd 组件)small | middle | large-
validateMessages验证提示模板,说明见下ValidateMessages-
wrapperCol需要为输入控件设置布局样式时,使用该属性,用法同 labelColobject-
onFinish提交表单且数据验证成功后回调事件Function(values)-
onFinishFailed提交表单且数据验证失败后回调事件Function({ values, errorFields, outOfDate })-
onFieldsChange字段更新时触发回调事件Function(changedFields, allFields)-
onValuesChange字段值更新时触发回调事件Function(changedValues, allValues)-

validateMessages

Form 为验证提供了默认的错误提示信息,你可以通过配置 validateMessages 属性,修改对应的提示模板。一种常见的使用方式,是配置国际化提示信息:

  1. const validateMessages = {
  2. required: "'${name}' 是必选字段",
  3. // ...
  4. };
  5. <Form validateMessages={validateMessages} />;

此外,ConfigProvider 也提供了全局化配置方案,允许统一配置错误提示模板:

  1. const validateMessages = {
  2. required: "'${name}' 是必选字段",
  3. // ...
  4. };
  5. <ConfigProvider form={{ validateMessages }}>
  6. <Form />
  7. </ConfigProvider>;

Form.Item

表单字段组件,用于数据双向绑定、校验、布局等。

参数说明类型默认值
colon配合 label 属性使用,表示是否显示 label 后面的冒号booleantrue
dependencies设置依赖字段,说明见下NamePath[]-
extra额外的提示信息,和 help 类似,当需要错误信息和提示文案同时出现时,可以使用这个。string|ReactNode-
getValueFromEvent设置如何将 event 的值转换成字段值(..args: any[]) => any-
hasFeedback配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用booleanfalse
help提示信息,如不设置,则会根据校验规则自动生成string|ReactNode-
htmlFor设置子元素 label htmlFor 属性string-
noStyletrue 时不带样式,作为纯字段控件使用booleanfalse
labellabel 标签的文本string|ReactNode-
labelAlign标签文本对齐方式left | rightright
labelCollabel 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12}sm: {span: 3, offset: 12}。你可以通过 Form 的 labelCol 进行统一设置。当和 Form 同时设置时,以 Item 为准object-
name字段名,支持数组NamePath-
normalize转换字段值给控件(value, prevValue, prevValues) => any-
required是否必填,如不设置,则会根据校验规则自动生成booleanfalse
rules校验规则,设置字段的校验逻辑。点击此处查看示例Rule[]-
shouldUpdate自定义字段更新逻辑,说明见下boolean | (prevValue, curValue) => booleanfalse
trigger设置收集字段值变更的时机stringonChange
validateFirst当某一规则校验不通过时,是否停止剩下的规则的校验booleanfalse
validateStatus校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating'string-
validateTrigger设置字段校验的时机string | string[]onChange
valuePropName子节点的值的属性,如 Switch 的是 'checked'string'value'
wrapperCol需要为输入控件设置布局样式时,使用该属性,用法同 labelCol。你可以通过 Form 的 wrapperCol 进行统一设置。当和 Form 同时设置时,以 Item 为准。object-

dependencies

当字段间存在依赖关系时使用。如果一个字段设置了 dependencies 属性。那么它所依赖的字段更新时,该字段将自动触发更新与校验。一种常见的场景,就是注册用户表单的“密码”与“确认密码”字段。“确认密码”校验依赖于“密码”字段,设置 dependencies 后,“密码”字段更新会重新触发“校验密码”的校验逻辑。你可以参考具体例子

shouldUpdate

Form 通过增量更新方式,只更新被修改的字段相关组件以达到性能优化目的。大部分场景下,你只需要编写代码或者与 dependencies 属性配合校验即可。而在某些特定场景,例如修改某个字段值后出现新的字段选项、或者纯粹希望表单任意变化都对某一个区域进行渲染。你可以通过 shouldUpdate 修改 Form.Item 的更新逻辑。

shouldUpdatetrue 时,Form 的任意变化都会使该 Form.Item 重新渲染。这对于自定义渲染一些区域十分有帮助:

  1. <Form.Item shouldUpdate>
  2. {() => {
  3. return <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>;
  4. }}
  5. </Form.Item>

你可以参考示例查看具体使用场景。

shouldUpdate 为方法时,表单的每次数值更新都会调用该方法,提供原先的值与当前的值以供你比较是否需要更新。这对于是否根据值来渲染额外字段十分有帮助:

<Form.Item shouldUpdate={(prevValues, curValues) => prevValues.additional !== curValues.additional}>
  {() => {
    return (
      <Form.Item name="other">
        <Input />
      </Form.Item>
    );
  }}
</Form.Item>

你可以参考示例查看具体使用场景。

Form.List

为字段提供数组化管理。

参数说明类型默认值
name字段名,支持数组NamePath-
children渲染函数(fields: Field[], operation: { add, remove, move }) => React.ReactNode-
<Form.List>
  {fields => (
    <div>
      {fields.map(field => (
        <Form.Item {...field}>
          <Input />
        </Form.Item>
      ))}
    </div>
  )}
</Form.List>

Form.Provider

提供表单间联动功能,其下设置 name 的 Form 更新时,会自动触发对应事件。查看示例

参数说明类型默认值
onFormChange子表单字段更新时触发Function(formName: string, info: { changedFields, forms })-
onFormFinish子表单提交时触发Function(formName: string, info: { values, forms })-
<Form.Provider
  onFormFinish={name => {
    if (name === 'form1') {
      // Do something...
    }
  }}
>
  <Form name="form1">...</Form>
  <Form name="form2">...</Form>
</Form.Provider>

FormInstance

名称说明类型
getFieldValue获取对应字段名的值(name: NamePath) => any
getFieldsValue获取一组字段名对应的值,会按照对应结构返回(nameList?: NamePath[], filterFunc?: (meta: { touched: boolean, validating: boolean }) => boolean) => any
getFieldError获取对应字段名的错误信息(name: NamePath) => string[]
getFieldsError获取一组字段名对应的错误信息,返回为数组形式(nameList?: NamePath[]) => FieldError[]
isFieldTouched检查对应字段是否被用户操作过(name: NamePath) => boolean
isFieldsTouched检查一组字段是否被用户操作过,allTouchedtrue 时检查是否所有字段都被操作过(nameList?: NamePath[], allTouched?: boolean) => boolean
isFieldValidating检查一组字段是否正在校验(name: NamePath) => boolean
resetFields重置一组字段到 initialValues(fields?: NamePath[]) => void
scrollToField滚动到对应字段位置(name: NamePath, options: [ScrollOptions]) => void
setFields设置一组字段状态(fields: FieldData[]) => void
setFieldsValue设置表单的值(values) => void
submit提交表单,与点击 submit 按钮效果相同() => void
validateFields触发表单验证(nameList?: NamePath[]) => Promise

validateFields 返回示例

validateFields()
  .then(values => {
    /*
  values:
    {
      username: 'username',
      password: 'password',
    }
  */
  })
  .catch(errorInfo => {
    /*
    errorInfo:
      {
        values: {
          username: 'username',
          password: 'password',
        },
        errorFields: [
          { password: ['username'], errors: ['Please input your Password!'] },
        ],
        outOfDate: false,
      }
    */
  });

Interface

NamePath

string | number | (string | number)[]

FieldData

名称说明类型
touched是否被用户操作过boolean
validating是否正在校验boolean
errors错误信息string[]
name字段名称NamePath[]
value字段对应值any

Rule

Rule 支持接收 object 进行配置,也支持 function 来动态获取 from 的数据:

type Rule = RuleConfig | ((form: FormInstance) => RuleConfig);
名称说明类型
enum是否匹配枚举中的值any[]
lenstring 类型时为字符串长度;number 类型时为确定数字; array 类型时为数组长度number
maxstring 类型为字符串最大长度;number 类型时为最大值;array 类型时为数组最大长度number
message错误信息,不设置时会通过模板自动生成string
minstring 类型为字符串最小长度;number 类型时为最小值;array 类型时为数组最小长度number
pattern正则表达式匹配RegExp
required是否为必选字段boolean
transform将字段值转换成目标值后进行校验(value) => any
type类型,常见有 string |number |boolean |url | email。更多请参考此处string
validator自定义校验,接收 Promise 作为返回值。示例参考(rule, value) => Promise
whitespace如果字段仅包含空格则校验不通过boolean
validateTrigger设置触发验证时机,必须是 Form.Item 的 validateTrigger 的子集string | string[]

从 v3 升级到 v4

如果你是 antd v3 的用户,你可以参考迁移示例

FAQ

自定义 validator 没有效果

这是由于你的 validator 有错误导致 callback 没有执行到。你可以选择通过 async 返回一个 promise 或者使用 try…catch 进行错误捕获:

validator: async (rule, value) => {
  throw new Error('Something wrong!');
}

// or

validator(rule, value, callback) => {
  try {
    throw new Error('Something wrong!');
  } catch (err) {
    callback(err);
  }
}

为何在 Modal 中调用 form 控制台会报错?

Warning: Instance created by useForm is not connect to any Form element. Forget to pass form prop?

这是因为你在调用 form 方法时,Modal 还未初始化导致 form 没有关联任何 Form 组件。你可以通过给 Modal 设置 forceRender 将其预渲染。示例点击此处