Form表单

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

何时使用

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

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

代码演示

Form表单 - 图1

基本使用

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

TypeScript

JavaScript

Form表单 - 图2

  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: any) => {
  11. console.log('Success:', values);
  12. };
  13. const onFinishFailed = (errorInfo: any) => {
  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表单 - 图3

表单方法调用

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

注意 useFormReact Hooks 的实现,只能用于函数组件,class 组件请查看下面的例子。

TypeScript

JavaScript

Form表单 - 图4

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

Form表单 - 图5

表单方法调用(Class component)

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

TypeScript

JavaScript

Form表单 - 图6

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

Form表单 - 图7

表单布局

表单有三种布局。

TypeScript

JavaScript

Form表单 - 图8

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

Form表单 - 图9

必选样式

通过 requiredMark 切换必选与可选样式。

TypeScript

JavaScript

Form表单 - 图10

  1. import React, { useState } from 'react';
  2. import { Form, Input, Button, Radio } from 'antd';
  3. import { InfoCircleOutlined } from '@ant-design/icons';
  4. type RequiredMark = boolean | 'optional';
  5. const FormLayoutDemo = () => {
  6. const [form] = Form.useForm();
  7. const [requiredMark, setRequiredMarkType] = useState<RequiredMark>('optional');
  8. const onRequiredTypeChange = ({ requiredMarkValue }: { requiredMarkValue: RequiredMark }) => {
  9. setRequiredMarkType(requiredMarkValue);
  10. };
  11. return (
  12. <Form
  13. form={form}
  14. layout="vertical"
  15. initialValues={{ requiredMarkValue: requiredMark }}
  16. onValuesChange={onRequiredTypeChange}
  17. requiredMark={requiredMark}
  18. >
  19. <Form.Item label="Required Mark" name="requiredMarkValue">
  20. <Radio.Group>
  21. <Radio.Button value="optional">Optional</Radio.Button>
  22. <Radio.Button value>Required</Radio.Button>
  23. <Radio.Button value={false}>Hidden</Radio.Button>
  24. </Radio.Group>
  25. </Form.Item>
  26. <Form.Item label="Field A" required tooltip="This is a required field">
  27. <Input placeholder="input placeholder" />
  28. </Form.Item>
  29. <Form.Item
  30. label="Field B"
  31. tooltip={{ title: 'Tooltip with customize icon', icon: <InfoCircleOutlined /> }}
  32. >
  33. <Input placeholder="input placeholder" />
  34. </Form.Item>
  35. <Form.Item>
  36. <Button type="primary">Submit</Button>
  37. </Form.Item>
  38. </Form>
  39. );
  40. };
  41. ReactDOM.render(<FormLayoutDemo />, mountNode);

Form表单 - 图11

表单尺寸

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

TypeScript

JavaScript

Form表单 - 图12

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

Form表单 - 图13

动态增减表单项

动态增加、减少表单项。add 方法参数可用于设置初始值。

  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
  26. name="names"
  27. rules={[
  28. {
  29. validator: async (_, names) => {
  30. if (!names || names.length < 2) {
  31. return Promise.reject(new Error('At least 2 passengers'));
  32. }
  33. },
  34. },
  35. ]}
  36. >
  37. {(fields, { add, remove }, { errors }) => (
  38. <>
  39. {fields.map((field, index) => (
  40. <Form.Item
  41. {...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
  42. label={index === 0 ? 'Passengers' : ''}
  43. required={false}
  44. key={field.key}
  45. >
  46. <Form.Item
  47. {...field}
  48. validateTrigger={['onChange', 'onBlur']}
  49. rules={[
  50. {
  51. required: true,
  52. whitespace: true,
  53. message: "Please input passenger's name or delete this field.",
  54. },
  55. ]}
  56. noStyle
  57. >
  58. <Input placeholder="passenger name" style={{ width: '60%' }} />
  59. </Form.Item>
  60. {fields.length > 1 ? (
  61. <MinusCircleOutlined
  62. className="dynamic-delete-button"
  63. onClick={() => remove(field.name)}
  64. />
  65. ) : null}
  66. </Form.Item>
  67. ))}
  68. <Form.Item>
  69. <Button
  70. type="dashed"
  71. onClick={() => add()}
  72. style={{ width: '60%' }}
  73. icon={<PlusOutlined />}
  74. >
  75. Add field
  76. </Button>
  77. <Button
  78. type="dashed"
  79. onClick={() => {
  80. add('The head item', 0);
  81. }}
  82. style={{ width: '60%', marginTop: '20px' }}
  83. icon={<PlusOutlined />}
  84. >
  85. Add field at head
  86. </Button>
  87. <Form.ErrorList errors={errors} />
  88. </Form.Item>
  89. </>
  90. )}
  91. </Form.List>
  92. <Form.Item>
  93. <Button type="primary" htmlType="submit">
  94. Submit
  95. </Button>
  96. </Form.Item>
  97. </Form>
  98. );
  99. };
  100. ReactDOM.render(<DynamicFieldSet />, mountNode);
  1. .dynamic-delete-button {
  2. position: relative;
  3. top: 4px;
  4. margin: 0 8px;
  5. color: #999;
  6. font-size: 24px;
  7. cursor: pointer;
  8. transition: all 0.3s;
  9. }
  10. .dynamic-delete-button:hover {
  11. color: #777;
  12. }
  13. .dynamic-delete-button[disabled] {
  14. cursor: not-allowed;
  15. opacity: 0.5;
  16. }

Form表单 - 图14

动态增减嵌套字段

嵌套表单字段需要对 field 进行拓展,将 field.namefield.fieldKey 应用于控制字段。

  1. import { Form, Input, Button, Space } from 'antd';
  2. import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
  3. const Demo = () => {
  4. const onFinish = values => {
  5. console.log('Received values of form:', values);
  6. };
  7. return (
  8. <Form name="dynamic_form_nest_item" onFinish={onFinish} autoComplete="off">
  9. <Form.List name="users">
  10. {(fields, { add, remove }) => (
  11. <>
  12. {fields.map(({ key, name, fieldKey, ...restField }) => (
  13. <Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
  14. <Form.Item
  15. {...restField}
  16. name={[name, 'first']}
  17. fieldKey={[fieldKey, 'first']}
  18. rules={[{ required: true, message: 'Missing first name' }]}
  19. >
  20. <Input placeholder="First Name" />
  21. </Form.Item>
  22. <Form.Item
  23. {...restField}
  24. name={[name, 'last']}
  25. fieldKey={[fieldKey, 'last']}
  26. rules={[{ required: true, message: 'Missing last name' }]}
  27. >
  28. <Input placeholder="Last Name" />
  29. </Form.Item>
  30. <MinusCircleOutlined onClick={() => remove(name)} />
  31. </Space>
  32. ))}
  33. <Form.Item>
  34. <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
  35. Add field
  36. </Button>
  37. </Form.Item>
  38. </>
  39. )}
  40. </Form.List>
  41. <Form.Item>
  42. <Button type="primary" htmlType="submit">
  43. Submit
  44. </Button>
  45. </Form.Item>
  46. </Form>
  47. );
  48. };
  49. ReactDOM.render(<Demo />, mountNode);

Form表单 - 图15

复杂的动态增减表单项

这个例子演示了一个表单中包含多个表单控件的情况。

  1. import { Form, Input, Button, Space, Select } from 'antd';
  2. import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
  3. const { Option } = Select;
  4. const areas = [
  5. { label: 'Beijing', value: 'Beijing' },
  6. { label: 'Shanghai', value: 'Shanghai' },
  7. ];
  8. const sights = {
  9. Beijing: ['Tiananmen', 'Great Wall'],
  10. Shanghai: ['Oriental Pearl', 'The Bund'],
  11. };
  12. const Demo = () => {
  13. const [form] = Form.useForm();
  14. const onFinish = values => {
  15. console.log('Received values of form:', values);
  16. };
  17. const handleChange = () => {
  18. form.setFieldsValue({ sights: [] });
  19. };
  20. return (
  21. <Form form={form} name="dynamic_form_nest_item" onFinish={onFinish} autoComplete="off">
  22. <Form.Item name="area" label="Area" rules={[{ required: true, message: 'Missing area' }]}>
  23. <Select options={areas} onChange={handleChange} />
  24. </Form.Item>
  25. <Form.List name="sights">
  26. {(fields, { add, remove }) => (
  27. <>
  28. {fields.map(field => (
  29. <Space key={field.key} align="baseline">
  30. <Form.Item
  31. noStyle
  32. shouldUpdate={(prevValues, curValues) =>
  33. prevValues.area !== curValues.area || prevValues.sights !== curValues.sights
  34. }
  35. >
  36. {() => (
  37. <Form.Item
  38. {...field}
  39. label="Sight"
  40. name={[field.name, 'sight']}
  41. fieldKey={[field.fieldKey, 'sight']}
  42. rules={[{ required: true, message: 'Missing sight' }]}
  43. >
  44. <Select disabled={!form.getFieldValue('area')} style={{ width: 130 }}>
  45. {(sights[form.getFieldValue('area')] || []).map(item => (
  46. <Option key={item} value={item}>
  47. {item}
  48. </Option>
  49. ))}
  50. </Select>
  51. </Form.Item>
  52. )}
  53. </Form.Item>
  54. <Form.Item
  55. {...field}
  56. label="Price"
  57. name={[field.name, 'price']}
  58. fieldKey={[field.fieldKey, 'price']}
  59. rules={[{ required: true, message: 'Missing price' }]}
  60. >
  61. <Input />
  62. </Form.Item>
  63. <MinusCircleOutlined onClick={() => remove(field.name)} />
  64. </Space>
  65. ))}
  66. <Form.Item>
  67. <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
  68. Add sights
  69. </Button>
  70. </Form.Item>
  71. </>
  72. )}
  73. </Form.List>
  74. <Form.Item>
  75. <Button type="primary" htmlType="submit">
  76. Submit
  77. </Button>
  78. </Form.Item>
  79. </Form>
  80. );
  81. };
  82. ReactDOM.render(<Demo />, mountNode);

Form表单 - 图16

嵌套结构与校验信息

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

TypeScript

JavaScript

Form表单 - 图17

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

Form表单 - 图18

复杂一点的控件

这里演示 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, Space, Typography } 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. <Space>
  11. <Form.Item
  12. name="username"
  13. noStyle
  14. rules={[{ required: true, message: 'Username is required' }]}
  15. >
  16. <Input style={{ width: 160 }} placeholder="Please input" />
  17. </Form.Item>
  18. <Tooltip title="Useful information">
  19. <Typography.Link href="#API">Need Help?</Typography.Link>
  20. </Tooltip>
  21. </Space>
  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% - 8px)' }}
  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% - 8px)', margin: '0 8px' }}
  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表单 - 图19

自定义表单控件

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

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

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

TypeScript

JavaScript

Form表单 - 图20

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

Form表单 - 图21

表单数据存储于上层组件

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

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

TypeScript

JavaScript

Form表单 - 图22

  1. import React, { useState } from 'react';
  2. import { Form, Input } from 'antd';
  3. interface FieldData {
  4. name: string | number | (string | number)[];
  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. <Form
  16. name="global_state"
  17. layout="inline"
  18. fields={fields}
  19. onFieldsChange={(_, allFields) => {
  20. onChange(allFields);
  21. }}
  22. >
  23. <Form.Item
  24. name="username"
  25. label="Username"
  26. rules={[{ required: true, message: 'Username is required!' }]}
  27. >
  28. <Input />
  29. </Form.Item>
  30. </Form>
  31. );
  32. const Demo = () => {
  33. const [fields, setFields] = useState<FieldData[]>([{ name: ['username'], value: 'Ant Design' }]);
  34. return (
  35. <>
  36. <CustomizedForm
  37. fields={fields}
  38. onChange={newFields => {
  39. setFields(newFields);
  40. }}
  41. />
  42. <pre className="language-bash">{JSON.stringify(fields, null, 2)}</pre>
  43. </>
  44. );
  45. };
  46. ReactDOM.render(<Demo />, mountNode);

Form表单 - 图23

多表单联动

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

TypeScript

JavaScript

Form表单 - 图24

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

Form表单 - 图25

内联登录栏

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

TypeScript

JavaScript

Form表单 - 图26

  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: any) => {
  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>
  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表单 - 图27

登录框

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

🛎️ 想要 3 分钟实现?试试 ProForm

TypeScript

JavaScript

Form表单 - 图28

  1. import { Form, Input, Button, Checkbox } from 'antd';
  2. import { UserOutlined, LockOutlined } from '@ant-design/icons';
  3. const NormalLoginForm = () => {
  4. const onFinish = (values: any) => {
  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 .ant-col-rtl .login-form-forgot {
  8. float: left;
  9. }
  10. #components-form-demo-normal-login .login-form-button {
  11. width: 100%;
  12. }

Form表单 - 图29

注册新用户

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

TypeScript

JavaScript

Form表单 - 图30

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

Form表单 - 图31

高级搜索

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

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

🛎️ 想要 3 分钟实现? 试试 ProForm 的查询表单

TypeScript

JavaScript

Form表单 - 图32

  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: any) => {
  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={{ margin: '0 8px' }}
  48. onClick={() => {
  49. form.resetFields();
  50. }}
  51. >
  52. Clear
  53. </Button>
  54. <a
  55. style={{ 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. [data-theme='compact'] .ant-advanced-search-form,
  2. .ant-advanced-search-form {
  3. padding: 24px;
  4. background: #fbfbfb;
  5. border: 1px solid #d9d9d9;
  6. border-radius: 2px;
  7. }
  8. [data-theme='compact'] .ant-advanced-search-form .ant-form-item,
  9. .ant-advanced-search-form .ant-form-item {
  10. display: flex;
  11. }
  12. [data-theme='compact'] .ant-advanced-search-form .ant-form-item-control-wrapper,
  13. .ant-advanced-search-form .ant-form-item-control-wrapper {
  14. flex: 1;
  15. }

Form表单 - 图33

弹出层中的新建表单

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

🛎️ 想要 3 分钟实现?试试 ProForm 的 Modal 表单

TypeScript

JavaScript

Form表单 - 图34

  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: any) => {
  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表单 - 图35

时间类控件

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

TypeScript

JavaScript

Form表单 - 图36

  1. import { Form, DatePicker, TimePicker, Button } from 'antd';
  2. const { 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' as const, required: true, message: 'Please select time!' }],
  15. };
  16. const rangeConfig = {
  17. rules: [{ type: 'array' as const, required: true, message: 'Please select time!' }],
  18. };
  19. const TimeRelatedForm = () => {
  20. const onFinish = (fieldsValue: any) => {
  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. <DatePicker picker="month" />
  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表单 - 图37

自行处理表单数据

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

TypeScript

JavaScript

Form表单 - 图38

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

Form表单 - 图39

自定义校验

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

  1. validateStatus: 校验状态,可选 ‘success’, ‘warning’, ‘error’, ‘validating’。

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

  3. help:设置校验文案。

TypeScript

JavaScript

Form表单 - 图40

  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 allowClear>
  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={[{ value: 'xx', label: 'xx' }]} allowClear />
  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
  78. style={{ display: 'inline-block', width: '24px', lineHeight: '32px', textAlign: 'center' }}
  79. >
  80. -
  81. </span>
  82. <Form.Item style={{ display: 'inline-block', width: 'calc(50% - 12px)' }}>
  83. <DatePicker />
  84. </Form.Item>
  85. </Form.Item>
  86. <Form.Item label="Success" hasFeedback validateStatus="success">
  87. <InputNumber style={{ width: '100%' }} />
  88. </Form.Item>
  89. <Form.Item label="Success" hasFeedback validateStatus="success">
  90. <Input allowClear placeholder="with allowClear" />
  91. </Form.Item>
  92. <Form.Item label="Warning" hasFeedback validateStatus="warning">
  93. <Input.Password placeholder="with input password" />
  94. </Form.Item>
  95. <Form.Item label="Error" hasFeedback validateStatus="error">
  96. <Input.Password allowClear placeholder="with input password and allowClear" />
  97. </Form.Item>
  98. </Form>,
  99. mountNode,
  100. );

Form表单 - 图41

动态校验规则

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

TypeScript

JavaScript

Form表单 - 图42

  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: { target: { checked: boolean } }) => {
  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表单 - 图43

校验其他组件

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

TypeScript

JavaScript

Form表单 - 图44

  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: any) => {
  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: any) => {
  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
  97. name="radio-button"
  98. label="Radio.Button"
  99. rules={[{ required: true, message: 'Please pick an item!' }]}
  100. >
  101. <Radio.Group>
  102. <Radio.Button value="a">item 1</Radio.Button>
  103. <Radio.Button value="b">item 2</Radio.Button>
  104. <Radio.Button value="c">item 3</Radio.Button>
  105. </Radio.Group>
  106. </Form.Item>
  107. <Form.Item name="checkbox-group" label="Checkbox.Group">
  108. <Checkbox.Group>
  109. <Row>
  110. <Col span={8}>
  111. <Checkbox value="A" style={{ lineHeight: '32px' }}>
  112. A
  113. </Checkbox>
  114. </Col>
  115. <Col span={8}>
  116. <Checkbox value="B" style={{ lineHeight: '32px' }} disabled>
  117. B
  118. </Checkbox>
  119. </Col>
  120. <Col span={8}>
  121. <Checkbox value="C" style={{ lineHeight: '32px' }}>
  122. C
  123. </Checkbox>
  124. </Col>
  125. <Col span={8}>
  126. <Checkbox value="D" style={{ lineHeight: '32px' }}>
  127. D
  128. </Checkbox>
  129. </Col>
  130. <Col span={8}>
  131. <Checkbox value="E" style={{ lineHeight: '32px' }}>
  132. E
  133. </Checkbox>
  134. </Col>
  135. <Col span={8}>
  136. <Checkbox value="F" style={{ lineHeight: '32px' }}>
  137. F
  138. </Checkbox>
  139. </Col>
  140. </Row>
  141. </Checkbox.Group>
  142. </Form.Item>
  143. <Form.Item name="rate" label="Rate">
  144. <Rate />
  145. </Form.Item>
  146. <Form.Item
  147. name="upload"
  148. label="Upload"
  149. valuePropName="fileList"
  150. getValueFromEvent={normFile}
  151. extra="longgggggggggggggggggggggggggggggggggg"
  152. >
  153. <Upload name="logo" action="/upload.do" listType="picture">
  154. <Button icon={<UploadOutlined />}>Click to upload</Button>
  155. </Upload>
  156. </Form.Item>
  157. <Form.Item label="Dragger">
  158. <Form.Item name="dragger" valuePropName="fileList" getValueFromEvent={normFile} noStyle>
  159. <Upload.Dragger name="files" action="/upload.do">
  160. <p className="ant-upload-drag-icon">
  161. <InboxOutlined />
  162. </p>
  163. <p className="ant-upload-text">Click or drag file to this area to upload</p>
  164. <p className="ant-upload-hint">Support for a single or bulk upload.</p>
  165. </Upload.Dragger>
  166. </Form.Item>
  167. </Form.Item>
  168. <Form.Item wrapperCol={{ span: 12, offset: 6 }}>
  169. <Button type="primary" htmlType="submit">
  170. Submit
  171. </Button>
  172. </Form.Item>
  173. </Form>
  174. );
  175. };
  176. ReactDOM.render(<Demo />, mountNode);

API

Form

参数说明类型默认值版本
colon配置 Form.Item 的 colon 的默认值。表示是否显示 label 后面的冒号 (只有在属性 layout 为 horizontal 时有效)booleantrue
component设置 Form 渲染元素,为 false 则不创建 DOM 节点ComponentType | falseform
fields通过状态管理(如 redux)控制表单字段,如非强需求不推荐使用。查看示例FieldData[]-
formForm.useForm() 创建的 form 控制实例,不提供时会自动创建FormInstance-
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-
preserve当字段被删除时保留字段值booleantrue4.4.0
requiredMark必选样式,可以切换为必选或者可选展示样式。此为 Form 配置,Form.Item 无法单独配置boolean | optionaltrue4.6.0
scrollToFirstError提交失败自动滚动到第一个错误字段boolean | Optionsfalse
size设置字段组件的尺寸(仅限 antd 组件)small | middle | large-
validateMessages验证提示模板,说明见下ValidateMessages-
validateTrigger统一设置字段触发验证的时机string | string[]onChange4.3.0
wrapperCol需要为输入控件设置布局样式时,使用该属性,用法同 labelColobject-
onFieldsChange字段更新时触发回调事件function(changedFields, allFields)-
onFinish提交表单且数据验证成功后回调事件function(values)-
onFinishFailed提交表单且数据验证失败后回调事件function({ values, errorFields, outOfDate })-
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 类似,当需要错误信息和提示文案同时出现时,可以使用这个。ReactNode-
getValueFromEvent设置如何将 event 的值转换成字段值(..args: any[]) => any-
getValueProps为子元素添加额外的属性(value: any) => any-4.2.0
hasFeedback配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用booleanfalse
help提示信息,如不设置,则会根据校验规则自动生成ReactNode-
hidden是否隐藏字段(依然会收集和校验字段)booleanfalse4.4.0
htmlFor设置子元素 label htmlFor 属性string-
initialValue设置子元素默认值,如果与 Form 的 initialValues 冲突则以 Form 为准string-4.2.0
labellabel 标签的文本ReactNode-
labelAlign标签文本对齐方式left | rightright
labelCollabel 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12}sm: {span: 3, offset: 12}。你可以通过 Form 的 labelCol 进行统一设置,,不会作用于嵌套 Item。当和 Form 同时设置时,以 Item 为准object-
messageVariables默认验证字段的信息Record<string, string>-4.7.0
name字段名,支持数组NamePath-
normalize组件获取值后进行转换,再放入 Form 中。不支持异步(value, prevValue, prevValues) => any-
noStyletrue 时不带样式,作为纯字段控件使用booleanfalse
preserve当字段被删除时保留字段值booleantrue4.4.0
required必填样式设置。如不设置,则会根据校验规则自动生成booleanfalse
rules校验规则,设置字段的校验逻辑。点击此处查看示例Rule[]-
shouldUpdate自定义字段更新逻辑,说明见下boolean | (prevValue, curValue) => booleanfalse
tooltip配置提示信息ReactNode | TooltipProps & { icon: ReactNode }-4.7.0
trigger设置收集字段值变更的时机。点击此处查看示例stringonChange
validateFirst当某一规则校验不通过时,是否停止剩下的规则的校验。设置 parallel 时会并行校验boolean | parallelfalseparallel: 4.5.0
validateStatus校验状态,如不设置,则会根据校验规则自动生成,可选:’success’ ‘warning’ ‘error’ ‘validating’string-
validateTrigger设置字段校验的时机string | string[]onChange
valuePropName子节点的值的属性,如 Switch 的是 ‘checked’。该属性为 getValueProps 的封装,自定义 getValueProps 后会失效stringvalue
wrapperCol需要为输入控件设置布局样式时,使用该属性,用法同 labelCol。你可以通过 Form 的 wrapperCol 进行统一设置,不会作用于嵌套 Item。当和 Form 同时设置时,以 Item 为准object-

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

  1. 不再需要也不应该onChange 来做数据收集同步(你可以使用 Form 的 onValuesChange),但还是可以继续监听 onChange 事件。

  2. 你不能用控件的 valuedefaultValue 等属性来设置表单域的值,默认值可以用 Form 里的 initialValues 来设置。注意 initialValues 不能被 setState 动态更新,你需要用 setFieldsValue 来更新。

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

dependencies

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

dependencies 不应和 shouldUpdate 一起使用,因为这可能带来更新逻辑的混乱。

4.5.0 版本开始,dependencies 支持使用 render props 类型 children 的 Form.Item

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

  1. <Form.Item shouldUpdate={(prevValues, curValues) => prevValues.additional !== curValues.additional}>
  2. {() => {
  3. return (
  4. <Form.Item name="other">
  5. <Input />
  6. </Form.Item>
  7. );
  8. }}
  9. </Form.Item>

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

messageVariables

你可以通过 messageVariables 修改 Form.Item 的默认验证信息。

  1. <Form>
  2. <Form.Item messageVariables={{ another: 'good' }} label="user">
  3. <Input />
  4. </Form.Item>
  5. <Form.Item messageVariables={{ label: 'good' }} label={<span>user</span>}>
  6. <Input />
  7. </Form.Item>
  8. </Form>

Form.List

为字段提供数组化管理。

参数说明类型默认值版本
children渲染函数(fields: Field[], operation: { add, remove, move }, meta: { errors }) => React.ReactNode-
initialValue设置子元素默认值,如果与 Form 的 initialValues 冲突则以 Form 为准any[]-4.9.0
name字段名,支持数组NamePath-
rules校验规则,仅支持自定义规则。需要配合 ErrorList 一同使用。{ validator, message }[]-4.7.0
  1. <Form.List>
  2. {fields =>
  3. fields.map(field => (
  4. <Form.Item {...field}>
  5. <Input />
  6. </Form.Item>
  7. ))
  8. }
  9. </Form.List>

注意:Form.List 下的字段不应该配置 initialValue,你始终应该通过 Form.List 的 initialValue 或者 Form 的 initialValues 来配置。

operation

Form.List 渲染表单相关操作函数。

参数说明类型默认值
add新增表单项(defaultValue?: any, insertIndex?: number) => voidinsertIndex: 4.6.0
move移动表单项(from: number, to: number) => void-
remove删除表单项(index: number | number[]) => voidnumber[]: 4.5.0

Form.ErrorList

4.7.0 新增。错误展示组件,仅限配合 Form.List 的 rules 一同使用。参考示例

参数说明类型默认值
errors错误列表ReactNode[]-

Form.Provider

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

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

FormInstance

名称说明类型版本
getFieldError获取对应字段名的错误信息(name: NamePath) => string[]
getFieldInstance获取对应字段实例(name: NamePath) => any4.4.0
getFieldsError获取一组字段名对应的错误信息,返回为数组形式(nameList?: NamePath[]) => FieldError[]
getFieldsValue获取一组字段名对应的值,会按照对应结构返回。默认返回现存字段值,当调用 getFieldsValue(true) 时返回所有值(nameList?: NamePath[], filterFunc?: (meta: { touched: boolean, validating: boolean }) => boolean) => any
getFieldValue获取对应字段名的值(name: NamePath) => any
isFieldsTouched检查一组字段是否被用户操作过,allTouchedtrue 时检查是否所有字段都被操作过(nameList?: NamePath[], allTouched?: boolean) => boolean
isFieldTouched检查对应字段是否被用户操作过(name: NamePath) => 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 返回示例

  1. validateFields()
  2. .then(values => {
  3. /*
  4. values:
  5. {
  6. username: 'username',
  7. password: 'password',
  8. }
  9. */
  10. })
  11. .catch(errorInfo => {
  12. /*
  13. errorInfo:
  14. {
  15. values: {
  16. username: 'username',
  17. password: 'password',
  18. },
  19. errorFields: [
  20. { name: ['password'], errors: ['Please input your Password!'] },
  21. ],
  22. outOfDate: false,
  23. }
  24. */
  25. });

Interface

NamePath

string | number | (string | number)[]

FieldData

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

Rule

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

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

从 v3 升级到 v4

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

FAQ

自定义 validator 没有效果

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

  1. validator: async (rule, value) => {
  2. throw new Error('Something wrong!');
  3. }
  4. // or
  5. validator(rule, value, callback) => {
  6. try {
  7. throw new Error('Something wrong!');
  8. } catch (err) {
  9. callback(err);
  10. }
  11. }

name 为数组时的转换规则?

name 为数组时,会按照顺序填充路径。当存在数字且 form store 中没有该字段时会自动转变成数组。因而如果需要数组为 key 时请使用 string 如:['1', 'name']

为何在 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 将其预渲染。示例点击此处

为什么 Form.Item 下的子组件 defaultValue 不生效?

当你为 Form.Item 设置 name 属性后,子组件会转为受控模式。因而 defaultValue 不会生效。你需要在 Form 上通过 initialValues 设置默认值。

为什么第一次调用 ref 的 From 为空?

ref 仅在节点被加载时才会被赋值,请参考 React 官方文档:https://reactjs.org/docs/refs-and-the-dom.html#accessing-refs

为什么 resetFields 会重新 mount 组件?

resetFields 会重置整个 Field,因而其子组件也会重新 mount 从而消除自定义组件可能存在的副作用(例如异步数据、状态等等)。

Form 的 initialValues 与 Item 的 initialValue 区别?

在大部分场景下,我们总是推荐优先使用 Form 的 initialValues。只有存在动态字段时你才应该使用 Item 的 initialValue。默认值遵循以下规则:

  1. Form 的 initialValues 拥有最高优先级

  2. Field 的 initialValue 次之 *. 多个同 name Item 都设置 initialValue 时,则 Item 的 initialValue 不生效

为什么字段设置 rules 后更改值 onFieldsChange 会触发三次?

字段除了本身的值变化外,校验也是其状态之一。因而在触发字段变化会经历以下几个阶段:

  1. Trigger value change

  2. Rule validating

  3. Rule validated

在触发过程中,调用 isFieldValidating 会经历 false > true > false 的变化过程。

为什么 Form.List 不支持 label 还需要使用 ErrorList 展示错误?

Form.List 本身是 renderProps,内部样式非常自由。因而默认配置 labelerror 节点很难与之配合。如果你需要 antd 样式的 label,可以通过外部包裹 Form.Item 来实现。

为什么 Form.Item 的 dependencies 对 Form.List 下的字段没有效果?

Form.List 下的字段需要包裹 Form.List 本身的 name,比如:

  1. <Form.List name="users">
  2. {fields =>
  3. fields.map(field => (
  4. <React.Fragment key={field.key}>
  5. <Form.Item name={[field.name, 'name']} {...someRest1} />
  6. <Form.Item name={[field.name, 'age']} {...someRest1} />
  7. </React.Fragment>
  8. ))
  9. }
  10. </Form.List>

依赖则是:['users', 0, 'name']

为什么 normalize 不能是异步方法?

React 中异步更新会导致受控组件交互行为异常。当用户交互触发 onChange 后,通过异步改变值会导致组件 value 不会立刻更新,使得组件呈现假死状态。如果你需要异步触发变更,请通过自定义组件实现内部异步状态。

自定义表单控件 scrollToFirstErrorscrollToField 失效?

类似问题:#28370 #27994

滚动依赖于表单控件元素上绑定的 id 字段,如果自定义控件没有将 id 赋到正确的元素上,这个功能将失效。你可以参考这个 codesandbox

setFieldsValue 不会触发 onFieldsChangeonValuesChange

是的,change 事件仅当用户交互才会触发。该设计是为了防止在 change 事件中调用 setFieldsValue 导致的循环问题。

有更多参考文档吗?