Skip to main content
Glama

antd-components-mcp

examples.md95.7 kB
## Form 组件示例 ### 基本使用 基本的表单数据域控制展示,包含布局、初始化、验证、提交。 ```tsx import React from 'react'; import type { FormProps } from 'antd'; import { Button, Checkbox, Form, Input } from 'antd'; type FieldType = { username?: string; password?: string; remember?: string; }; const onFinish: FormProps<FieldType>['onFinish'] = (values) => { console.log('Success:', values); }; const onFinishFailed: FormProps<FieldType>['onFinishFailed'] = (errorInfo) => { console.log('Failed:', errorInfo); }; const App: React.FC = () => ( <Form name="basic" labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }} initialValues={{ remember: true }} onFinish={onFinish} onFinishFailed={onFinishFailed} autoComplete="off" > <Form.Item<FieldType> label="Username" name="username" rules={[{ required: true, message: 'Please input your username!' }]} > <Input /> </Form.Item> <Form.Item<FieldType> label="Password" name="password" rules={[{ required: true, message: 'Please input your password!' }]} > <Input.Password /> </Form.Item> <Form.Item<FieldType> name="remember" valuePropName="checked" label={null}> <Checkbox>Remember me</Checkbox> </Form.Item> <Form.Item label={null}> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); export default App; ``` ### 表单方法调用 通过 `Form.useForm` 对表单数据域进行交互。 > 注意 `useForm` 是 [React Hooks](https://reactjs.org/docs/hooks-intro.html) 的实现,只能用于函数组件。如果是在 Class Component 下,你也可以通过 `ref` 获取数据域:https://codesandbox.io/p/sandbox/ngtjtm ```tsx import React from 'react'; import { Button, Form, Input, Select, Space } from 'antd'; const { Option } = Select; const layout = { labelCol: { span: 8 }, wrapperCol: { span: 16 }, }; const tailLayout = { wrapperCol: { offset: 8, span: 16 }, }; const App: React.FC = () => { const [form] = Form.useForm(); const onGenderChange = (value: string) => { switch (value) { case 'male': form.setFieldsValue({ note: 'Hi, man!' }); break; case 'female': form.setFieldsValue({ note: 'Hi, lady!' }); break; case 'other': form.setFieldsValue({ note: 'Hi there!' }); break; default: } }; const onFinish = (values: any) => { console.log(values); }; const onReset = () => { form.resetFields(); }; const onFill = () => { form.setFieldsValue({ note: 'Hello world!', gender: 'male' }); }; return ( <Form {...layout} form={form} name="control-hooks" onFinish={onFinish} style={{ maxWidth: 600 }} > <Form.Item name="note" label="Note" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item name="gender" label="Gender" rules={[{ required: true }]}> <Select placeholder="Select a option and change input text above" onChange={onGenderChange} allowClear > <Option value="male">male</Option> <Option value="female">female</Option> <Option value="other">other</Option> </Select> </Form.Item> <Form.Item noStyle shouldUpdate={(prevValues, currentValues) => prevValues.gender !== currentValues.gender} > {({ getFieldValue }) => getFieldValue('gender') === 'other' ? ( <Form.Item name="customizeGender" label="Customize Gender" rules={[{ required: true }]}> <Input /> </Form.Item> ) : null } </Form.Item> <Form.Item {...tailLayout}> <Space> <Button type="primary" htmlType="submit"> Submit </Button> <Button htmlType="button" onClick={onReset}> Reset </Button> <Button type="link" htmlType="button" onClick={onFill}> Fill form </Button> </Space> </Form.Item> </Form> ); }; export default App; ``` ### 表单布局 表单有三种布局。 ```tsx import React, { useState } from 'react'; import { Button, Form, Input, Radio } from 'antd'; type LayoutType = Parameters<typeof Form>[0]['layout']; const App: React.FC = () => { const [form] = Form.useForm(); const [formLayout, setFormLayout] = useState<LayoutType>('horizontal'); const onFormLayoutChange = ({ layout }: { layout: LayoutType }) => { setFormLayout(layout); }; return ( <Form layout={formLayout} form={form} initialValues={{ layout: formLayout }} onValuesChange={onFormLayoutChange} style={{ maxWidth: formLayout === 'inline' ? 'none' : 600 }} > <Form.Item label="Form Layout" name="layout"> <Radio.Group value={formLayout}> <Radio.Button value="horizontal">Horizontal</Radio.Button> <Radio.Button value="vertical">Vertical</Radio.Button> <Radio.Button value="inline">Inline</Radio.Button> </Radio.Group> </Form.Item> <Form.Item label="Field A"> <Input placeholder="input placeholder" /> </Form.Item> <Form.Item label="Field B"> <Input placeholder="input placeholder" /> </Form.Item> <Form.Item> <Button type="primary">Submit</Button> </Form.Item> </Form> ); }; export default App; ``` ### 表单混合布局 在 `Form.Item` 上单独定义 `layout`,可以做到一个表单多种布局。 ```tsx import React from 'react'; import { Divider, Form, Input } from 'antd'; const App: React.FC = () => ( <> <Form name="layout-multiple-horizontal" layout="horizontal"> <Form.Item label="horizontal" name="horizontal" rules={[{ required: true }]} labelCol={{ span: 4 }} wrapperCol={{ span: 20 }} > <Input /> </Form.Item> <Form.Item layout="vertical" label="vertical" name="vertical" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item layout="vertical" label="vertical2" name="vertical2" rules={[{ required: true }]}> <Input /> </Form.Item> </Form> <Divider /> <Form name="layout-multiple-vertical" layout="vertical"> <Form.Item label="vertical" name="vertical" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item label="vertical2" name="vertical2" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item layout="horizontal" label="horizontal" name="horizontal" rules={[{ required: true }]} labelCol={{ span: 4 }} wrapperCol={{ span: 20 }} > <Input /> </Form.Item> </Form> </> ); export default App; ``` ### 表单禁用 设置表单组件禁用,仅对 antd 组件有效。 ```tsx import React, { useState } from 'react'; import { PlusOutlined } from '@ant-design/icons'; import { Button, Cascader, Checkbox, ColorPicker, DatePicker, Form, Input, InputNumber, Radio, Rate, Select, Slider, Switch, TreeSelect, Upload, } from 'antd'; const { RangePicker } = DatePicker; const { TextArea } = Input; const normFile = (e: any) => { if (Array.isArray(e)) { return e; } return e?.fileList; }; const FormDisabledDemo: React.FC = () => { const [componentDisabled, setComponentDisabled] = useState<boolean>(true); return ( <> <Checkbox checked={componentDisabled} onChange={(e) => setComponentDisabled(e.target.checked)} > Form disabled </Checkbox> <Form labelCol={{ span: 4 }} wrapperCol={{ span: 14 }} layout="horizontal" disabled={componentDisabled} style={{ maxWidth: 600 }} > <Form.Item label="Checkbox" name="disabled" valuePropName="checked"> <Checkbox>Checkbox</Checkbox> </Form.Item> <Form.Item label="Radio"> <Radio.Group> <Radio value="apple"> Apple </Radio> <Radio value="pear"> Pear </Radio> </Radio.Group> </Form.Item> <Form.Item label="Input"> <Input /> </Form.Item> <Form.Item label="Select"> <Select> <Select.Option value="demo">Demo</Select.Option> </Select> </Form.Item> <Form.Item label="TreeSelect"> <TreeSelect treeData={[ { title: 'Light', value: 'light', children: [{ title: 'Bamboo', value: 'bamboo' }] }, ]} /> </Form.Item> <Form.Item label="Cascader"> <Cascader options={[ { value: 'zhejiang', label: 'Zhejiang', children: [ { value: 'hangzhou', label: 'Hangzhou', }, ], }, ]} /> </Form.Item> <Form.Item label="DatePicker"> <DatePicker /> </Form.Item> <Form.Item label="RangePicker"> <RangePicker /> </Form.Item> <Form.Item label="InputNumber"> <InputNumber /> </Form.Item> <Form.Item label="TextArea"> <TextArea rows={4} /> </Form.Item> <Form.Item label="Switch" valuePropName="checked"> <Switch /> </Form.Item> <Form.Item label="Upload" valuePropName="fileList" getValueFromEvent={normFile}> <Upload action="/upload.do" listType="picture-card"> <button style={{ color: 'inherit', cursor: 'inherit', border: 0, background: 'none' }} type="button" > <PlusOutlined /> <div style={{ marginTop: 8 }}>Upload</div> </button> </Upload> </Form.Item> <Form.Item label="Button"> <Button>Button</Button> </Form.Item> <Form.Item label="Slider"> <Slider /> </Form.Item> <Form.Item label="ColorPicker"> <ColorPicker /> </Form.Item> <Form.Item label="Rate"> <Rate /> </Form.Item> </Form> </> ); }; export default () => <FormDisabledDemo />; ``` ### 表单变体 改变表单内所有组件的变体,可选 `outlined` `filled` `borderless` `underlined` 四种形态。 ```tsx import React from 'react'; import { Button, Cascader, DatePicker, Form, Input, InputNumber, Mentions, Segmented, Select, TreeSelect, } from 'antd'; const { RangePicker } = DatePicker; const formItemLayout = { labelCol: { xs: { span: 24 }, sm: { span: 6 }, }, wrapperCol: { xs: { span: 24 }, sm: { span: 14 }, }, }; const App: React.FC = () => { const [form] = Form.useForm(); const variant = Form.useWatch('variant', form); return ( <Form {...formItemLayout} form={form} variant={variant || 'filled'} style={{ maxWidth: 600 }} initialValues={{ variant: 'filled' }} > <Form.Item label="Form variant" name="variant"> <Segmented options={['outlined', 'filled', 'borderless', 'underlined']} /> </Form.Item> <Form.Item label="Input" name="Input" rules={[{ required: true, message: 'Please input!' }]}> <Input /> </Form.Item> <Form.Item label="InputNumber" name="InputNumber" rules={[{ required: true, message: 'Please input!' }]} > <InputNumber style={{ width: '100%' }} /> </Form.Item> <Form.Item label="TextArea" name="TextArea" rules={[{ required: true, message: 'Please input!' }]} > <Input.TextArea /> </Form.Item> <Form.Item label="Mentions" name="Mentions" rules={[{ required: true, message: 'Please input!' }]} > <Mentions /> </Form.Item> <Form.Item label="Select" name="Select" rules={[{ required: true, message: 'Please input!' }]} > <Select /> </Form.Item> <Form.Item label="Cascader" name="Cascader" rules={[{ required: true, message: 'Please input!' }]} > <Cascader /> </Form.Item> <Form.Item label="TreeSelect" name="TreeSelect" rules={[{ required: true, message: 'Please input!' }]} > <TreeSelect /> </Form.Item> <Form.Item label="DatePicker" name="DatePicker" rules={[{ required: true, message: 'Please input!' }]} > <DatePicker /> </Form.Item> <Form.Item label="RangePicker" name="RangePicker" rules={[{ required: true, message: 'Please input!' }]} > <RangePicker /> </Form.Item> <Form.Item wrapperCol={{ offset: 6, span: 16 }}> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); }; export default App; ``` ### 必选样式 通过 `requiredMark` 切换必选与可选样式。 ```tsx import React, { useState } from 'react'; import { InfoCircleOutlined } from '@ant-design/icons'; import { Button, Form, Input, Radio, Tag } from 'antd'; type RequiredMark = boolean | 'optional' | 'customize'; const customizeRequiredMark = (label: React.ReactNode, { required }: { required: boolean }) => ( <> {required ? <Tag color="error">Required</Tag> : <Tag color="warning">optional</Tag>} {label} </> ); const App: React.FC = () => { const [form] = Form.useForm(); const [requiredMark, setRequiredMarkType] = useState<RequiredMark>('optional'); const onRequiredTypeChange = ({ requiredMarkValue }: { requiredMarkValue: RequiredMark }) => { setRequiredMarkType(requiredMarkValue); }; return ( <Form form={form} layout="vertical" initialValues={{ requiredMarkValue: requiredMark }} onValuesChange={onRequiredTypeChange} requiredMark={requiredMark === 'customize' ? customizeRequiredMark : requiredMark} > <Form.Item label="Required Mark" name="requiredMarkValue"> <Radio.Group> <Radio.Button value>Default</Radio.Button> <Radio.Button value="optional">Optional</Radio.Button> <Radio.Button value={false}>Hidden</Radio.Button> <Radio.Button value="customize">Customize</Radio.Button> </Radio.Group> </Form.Item> <Form.Item label="Field A" required tooltip="This is a required field"> <Input placeholder="input placeholder" /> </Form.Item> <Form.Item label="Field B" tooltip={{ title: 'Tooltip with customize icon', icon: <InfoCircleOutlined /> }} > <Input placeholder="input placeholder" /> </Form.Item> <Form.Item> <Button type="primary">Submit</Button> </Form.Item> </Form> ); }; export default App; ``` ### 表单尺寸 设置表单组件尺寸,仅对 antd 组件有效。 ```tsx import React, { useState } from 'react'; import { Button, Cascader, DatePicker, Form, Input, InputNumber, Radio, Select, Switch, TreeSelect, } from 'antd'; type SizeType = Parameters<typeof Form>[0]['size']; const App: React.FC = () => { const [componentSize, setComponentSize] = useState<SizeType | 'default'>('default'); const onFormLayoutChange = ({ size }: { size: SizeType }) => { setComponentSize(size); }; return ( <Form labelCol={{ span: 4 }} wrapperCol={{ span: 14 }} layout="horizontal" initialValues={{ size: componentSize }} onValuesChange={onFormLayoutChange} size={componentSize as SizeType} style={{ maxWidth: 600 }} > <Form.Item label="Form Size" name="size"> <Radio.Group> <Radio.Button value="small">Small</Radio.Button> <Radio.Button value="default">Default</Radio.Button> <Radio.Button value="large">Large</Radio.Button> </Radio.Group> </Form.Item> <Form.Item label="Input"> <Input /> </Form.Item> <Form.Item label="Select"> <Select> <Select.Option value="demo">Demo</Select.Option> </Select> </Form.Item> <Form.Item label="TreeSelect"> <TreeSelect treeData={[ { title: 'Light', value: 'light', children: [{ title: 'Bamboo', value: 'bamboo' }] }, ]} /> </Form.Item> <Form.Item label="Cascader"> <Cascader options={[ { value: 'zhejiang', label: 'Zhejiang', children: [{ value: 'hangzhou', label: 'Hangzhou' }], }, ]} /> </Form.Item> <Form.Item label="DatePicker"> <DatePicker /> </Form.Item> <Form.Item label="InputNumber"> <InputNumber /> </Form.Item> <Form.Item label="Switch" valuePropName="checked"> <Switch /> </Form.Item> <Form.Item label="Button"> <Button>Button</Button> </Form.Item> </Form> ); }; export default App; ``` ### 表单标签可换行 使用 `labelWrap` 可以开启 `label` 换行。 ```tsx import React from 'react'; import { Button, Form, Input } from 'antd'; const App: React.FC = () => ( <Form name="wrap" labelCol={{ flex: '110px' }} labelAlign="left" labelWrap wrapperCol={{ flex: 1 }} colon={false} style={{ maxWidth: 600 }} > <Form.Item label="Normal label" name="username" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item label="A super long label text" name="password" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item label="A super long label text" name="password1"> <Input /> </Form.Item> <Form.Item label=" "> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); export default App; ``` ### 非阻塞校验 `rule` 添加 `warningOnly` 后校验不再阻塞表单提交。 ```tsx import React from 'react'; import { Button, Form, Input, message, Space } from 'antd'; const App: React.FC = () => { const [form] = Form.useForm(); const onFinish = () => { message.success('Submit success!'); }; const onFinishFailed = () => { message.error('Submit failed!'); }; const onFill = () => { form.setFieldsValue({ url: 'https://taobao.com/', }); }; return ( <Form form={form} layout="vertical" onFinish={onFinish} onFinishFailed={onFinishFailed} autoComplete="off" > <Form.Item name="url" label="URL" rules={[{ required: true }, { type: 'url', warningOnly: true }, { type: 'string', min: 6 }]} > <Input placeholder="input placeholder" /> </Form.Item> <Form.Item> <Space> <Button type="primary" htmlType="submit"> Submit </Button> <Button htmlType="button" onClick={onFill}> Fill </Button> </Space> </Form.Item> </Form> ); }; export default App; ``` ### 字段监听 Hooks `useWatch` 允许你监听字段变化,同时仅当该字段变化时重新渲染。API 文档请[查阅此处](#formusewatch)。 ```tsx import React from 'react'; import { Form, Input, InputNumber, Typography } from 'antd'; const Demo: React.FC = () => { const [form] = Form.useForm<{ name: string; age: number }>(); const nameValue = Form.useWatch('name', form); // The selector is static and does not support closures. const customValue = Form.useWatch((values) => `name: ${values.name || ''}`, form); return ( <> <Form form={form} layout="vertical" autoComplete="off"> <Form.Item name="name" label="Name (Watch to trigger rerender)"> <Input /> </Form.Item> <Form.Item name="age" label="Age (Not Watch)"> <InputNumber /> </Form.Item> </Form> <Typography> <pre>Name Value: {nameValue}</pre> <pre>Custom Value: {customValue}</pre> </Typography> </> ); }; export default Demo; ``` ### 校验时机 对于有异步校验的场景,过于频繁的校验会导致后端压力。可以通过 `validateTrigger` 改变校验时机,或者 `validateDebounce` 改变校验频率,或者 `validateFirst` 设置校验短路。 ```tsx import React from 'react'; import { Alert, Form, Input } from 'antd'; const App: React.FC = () => ( <Form name="trigger" style={{ maxWidth: 600 }} layout="vertical" autoComplete="off"> <Alert message="Use 'max' rule, continue type chars to see it" /> <Form.Item hasFeedback label="Field A" name="field_a" validateTrigger="onBlur" rules={[{ max: 3 }]} > <Input placeholder="Validate required onBlur" /> </Form.Item> <Form.Item hasFeedback label="Field B" name="field_b" validateDebounce={1000} rules={[{ max: 3 }]} > <Input placeholder="Validate required debounce after 1s" /> </Form.Item> <Form.Item hasFeedback label="Field C" name="field_c" validateFirst rules={[{ max: 6 }, { max: 3, message: 'Continue input to exceed 6 chars' }]} > <Input placeholder="Validate one by one" /> </Form.Item> </Form> ); export default App; ``` ### 仅校验 通过 `validateFields` 的 `validateOnly` 可以动态调整提交按钮的 `disabled` 状态。 ```tsx import React from 'react'; import type { FormInstance } from 'antd'; import { Button, Form, Input, Space } from 'antd'; interface SubmitButtonProps { form: FormInstance; } const SubmitButton: React.FC<React.PropsWithChildren<SubmitButtonProps>> = ({ form, children }) => { const [submittable, setSubmittable] = React.useState<boolean>(false); // Watch all values const values = Form.useWatch([], form); React.useEffect(() => { form .validateFields({ validateOnly: true }) .then(() => setSubmittable(true)) .catch(() => setSubmittable(false)); }, [form, values]); return ( <Button type="primary" htmlType="submit" disabled={!submittable}> {children} </Button> ); }; const App: React.FC = () => { const [form] = Form.useForm(); return ( <Form form={form} name="validateOnly" layout="vertical" autoComplete="off"> <Form.Item name="name" label="Name" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item name="age" label="Age" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item> <Space> <SubmitButton form={form}>Submit</SubmitButton> <Button htmlType="reset">Reset</Button> </Space> </Form.Item> </Form> ); }; export default App; ``` ### 字段路径前缀 在某些场景,你希望统一设置一些字段的前缀。你可以通过 HOC 实现该效果。 ```tsx import React from 'react'; import { Button, Form, Input } from 'antd'; import type { FormItemProps } from 'antd'; const MyFormItemContext = React.createContext<(string | number)[]>([]); interface MyFormItemGroupProps { prefix: string | number | (string | number)[]; } function toArr(str: string | number | (string | number)[]): (string | number)[] { return Array.isArray(str) ? str : [str]; } const MyFormItemGroup: React.FC<React.PropsWithChildren<MyFormItemGroupProps>> = ({ prefix, children, }) => { const prefixPath = React.useContext(MyFormItemContext); const concatPath = React.useMemo(() => [...prefixPath, ...toArr(prefix)], [prefixPath, prefix]); return <MyFormItemContext.Provider value={concatPath}>{children}</MyFormItemContext.Provider>; }; const MyFormItem = ({ name, ...props }: FormItemProps) => { const prefixPath = React.useContext(MyFormItemContext); const concatName = name !== undefined ? [...prefixPath, ...toArr(name)] : undefined; return <Form.Item name={concatName} {...props} />; }; const App: React.FC = () => { const onFinish = (value: object) => { console.log(value); }; return ( <Form name="form_item_path" layout="vertical" onFinish={onFinish}> <MyFormItemGroup prefix={['user']}> <MyFormItemGroup prefix={['name']}> <MyFormItem name="firstName" label="First Name"> <Input /> </MyFormItem> <MyFormItem name="lastName" label="Last Name"> <Input /> </MyFormItem> </MyFormItemGroup> <MyFormItem name="age" label="Age"> <Input /> </MyFormItem> </MyFormItemGroup> <Button type="primary" htmlType="submit"> Submit </Button> </Form> ); }; export default App; ``` ### 动态增减表单项 动态增加、减少表单项。`add` 方法参数可用于设置初始值。 ```tsx import React from 'react'; import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { Button, Form, Input } from 'antd'; const formItemLayout = { labelCol: { xs: { span: 24 }, sm: { span: 4 }, }, wrapperCol: { xs: { span: 24 }, sm: { span: 20 }, }, }; const formItemLayoutWithOutLabel = { wrapperCol: { xs: { span: 24, offset: 0 }, sm: { span: 20, offset: 4 }, }, }; const App: React.FC = () => { const onFinish = (values: any) => { console.log('Received values of form:', values); }; return ( <Form name="dynamic_form_item" {...formItemLayoutWithOutLabel} onFinish={onFinish} style={{ maxWidth: 600 }} > <Form.List name="names" rules={[ { validator: async (_, names) => { if (!names || names.length < 2) { return Promise.reject(new Error('At least 2 passengers')); } }, }, ]} > {(fields, { add, remove }, { errors }) => ( <> {fields.map((field, index) => ( <Form.Item {...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)} label={index === 0 ? 'Passengers' : ''} required={false} key={field.key} > <Form.Item {...field} validateTrigger={['onChange', 'onBlur']} rules={[ { required: true, whitespace: true, message: "Please input passenger's name or delete this field.", }, ]} noStyle > <Input placeholder="passenger name" style={{ width: '60%' }} /> </Form.Item> {fields.length > 1 ? ( <MinusCircleOutlined className="dynamic-delete-button" onClick={() => remove(field.name)} /> ) : null} </Form.Item> ))} <Form.Item> <Button type="dashed" onClick={() => add()} style={{ width: '60%' }} icon={<PlusOutlined />} > Add field </Button> <Button type="dashed" onClick={() => { add('The head item', 0); }} style={{ width: '60%', marginTop: '20px' }} icon={<PlusOutlined />} > Add field at head </Button> <Form.ErrorList errors={errors} /> </Form.Item> </> )} </Form.List> <Form.Item> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); }; export default App; ``` ### 动态增减嵌套字段 嵌套表单字段需要对 `field` 进行拓展,将 `field.name` 应用于控制字段。 ```tsx import React from 'react'; import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { Button, Form, Input, Space } from 'antd'; const onFinish = (values: any) => { console.log('Received values of form:', values); }; const App: React.FC = () => ( <Form name="dynamic_form_nest_item" onFinish={onFinish} style={{ maxWidth: 600 }} autoComplete="off" > <Form.List name="users"> {(fields, { add, remove }) => ( <> {fields.map(({ key, name, ...restField }) => ( <Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline"> <Form.Item {...restField} name={[name, 'first']} rules={[{ required: true, message: 'Missing first name' }]} > <Input placeholder="First Name" /> </Form.Item> <Form.Item {...restField} name={[name, 'last']} rules={[{ required: true, message: 'Missing last name' }]} > <Input placeholder="Last Name" /> </Form.Item> <MinusCircleOutlined onClick={() => remove(name)} /> </Space> ))} <Form.Item> <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}> Add field </Button> </Form.Item> </> )} </Form.List> <Form.Item> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); export default App; ``` ### 动态增减嵌套纯字段 嵌套 `noStyle` 字段的动态表单示例。 ```tsx import React from 'react'; import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { Button, Form, Input, Space } from 'antd'; const onFinish = (values: any) => { console.log('Received values of form:', values); }; const App: React.FC = () => ( <Form name="dynamic_form_no_style" onFinish={onFinish} style={{ maxWidth: 600 }} autoComplete="off" > <Form.Item label="Users"> <Form.List name="users"> {(fields, { add, remove }) => ( <> {fields.map((field) => ( <Space key={field.key} style={{ marginBottom: 16 }}> <Form.Item noStyle name={[field.name, 'lastName']} rules={[{ required: true }]}> <Input placeholder="Last Name" /> </Form.Item> <Form.Item noStyle name={[field.name, 'firstName']} rules={[{ required: true }]}> <Input placeholder="First Name" /> </Form.Item> <MinusCircleOutlined onClick={() => { remove(field.name); }} /> </Space> ))} <Form.Item> <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}> Add field </Button> </Form.Item> </> )} </Form.List> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); export default App; ``` ### 复杂的动态增减表单项 多个 Form.List 嵌套的使用场景。 ```tsx import React from 'react'; import { CloseOutlined } from '@ant-design/icons'; import { Button, Card, Form, Input, Space, Typography } from 'antd'; const App: React.FC = () => { const [form] = Form.useForm(); return ( <Form labelCol={{ span: 6 }} wrapperCol={{ span: 18 }} form={form} name="dynamic_form_complex" style={{ maxWidth: 600 }} autoComplete="off" initialValues={{ items: [{}] }} > <Form.List name="items"> {(fields, { add, remove }) => ( <div style={{ display: 'flex', rowGap: 16, flexDirection: 'column' }}> {fields.map((field) => ( <Card size="small" title={`Item ${field.name + 1}`} key={field.key} extra={ <CloseOutlined onClick={() => { remove(field.name); }} /> } > <Form.Item label="Name" name={[field.name, 'name']}> <Input /> </Form.Item> {/* Nest Form.List */} <Form.Item label="List"> <Form.List name={[field.name, 'list']}> {(subFields, subOpt) => ( <div style={{ display: 'flex', flexDirection: 'column', rowGap: 16 }}> {subFields.map((subField) => ( <Space key={subField.key}> <Form.Item noStyle name={[subField.name, 'first']}> <Input placeholder="first" /> </Form.Item> <Form.Item noStyle name={[subField.name, 'second']}> <Input placeholder="second" /> </Form.Item> <CloseOutlined onClick={() => { subOpt.remove(subField.name); }} /> </Space> ))} <Button type="dashed" onClick={() => subOpt.add()} block> + Add Sub Item </Button> </div> )} </Form.List> </Form.Item> </Card> ))} <Button type="dashed" onClick={() => add()} block> + Add Item </Button> </div> )} </Form.List> <Form.Item noStyle shouldUpdate> {() => ( <Typography> <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre> </Typography> )} </Form.Item> </Form> ); }; export default App; ``` ### 嵌套结构与校验信息 `name` 属性支持嵌套数据结构。通过 `validateMessages` 或 `message` 自定义校验信息模板,模板内容可参考[此处](https://github.com/react-component/field-form/blob/master/src/utils/messages.ts)。 ```tsx import React from 'react'; import { Button, Form, Input, InputNumber } from 'antd'; const layout = { labelCol: { span: 8 }, wrapperCol: { span: 16 }, }; const validateMessages = { required: '${label} is required!', types: { email: '${label} is not a valid email!', number: '${label} is not a valid number!', }, number: { range: '${label} must be between ${min} and ${max}', }, }; const onFinish = (values: any) => { console.log(values); }; const App: React.FC = () => ( <Form {...layout} name="nest-messages" onFinish={onFinish} style={{ maxWidth: 600 }} validateMessages={validateMessages} > <Form.Item name={['user', 'name']} label="Name" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item name={['user', 'email']} label="Email" rules={[{ type: 'email' }]}> <Input /> </Form.Item> <Form.Item name={['user', 'age']} label="Age" rules={[{ type: 'number', min: 0, max: 99 }]}> <InputNumber /> </Form.Item> <Form.Item name={['user', 'website']} label="Website"> <Input /> </Form.Item> <Form.Item name={['user', 'introduction']} label="Introduction"> <Input.TextArea /> </Form.Item> <Form.Item label={null}> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); export default App; ``` ### 复杂一点的控件 这里演示 `Form.Item` 内有多个元素的使用方式。`<Form.Item name="field" />` 只会对它的直接子元素绑定表单功能,例如直接包裹了 Input/Select。如果控件前后还有一些文案或样式装点,或者一个表单项内有多个控件,你可以使用内嵌的 `Form.Item` 完成。你可以给 `Form.Item` 自定义 `style` 进行内联布局,或者添加 `noStyle` 作为纯粹的无样式绑定组件(类似 3.x 中的 `getFieldDecorator`)。 ```diff - <Form.Item label="Field" name="field"> - <Input /> - </Form.Item> + <Form.Item label="Field"> + <Form.Item name="field" noStyle><Input /></Form.Item> // 直接包裹才会绑定表单 + <span>description</span> + </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 只作为布局作用。 更复杂的封装复用方式可以参考下面的 `自定义表单控件` 演示。 ```tsx import React from 'react'; import { Button, Form, Input, Select, Space, Tooltip, Typography } from 'antd'; const { Option } = Select; const onFinish = (values: any) => { console.log('Received values of form: ', values); }; const App: React.FC = () => ( <Form name="complex-form" onFinish={onFinish} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }} > <Form.Item label="Username"> <Space> <Form.Item name="username" noStyle rules={[{ required: true, message: 'Username is required' }]} > <Input style={{ width: 160 }} placeholder="Please input" /> </Form.Item> <Tooltip title="Useful information"> <Typography.Link href="#API">Need Help?</Typography.Link> </Tooltip> </Space> </Form.Item> <Form.Item label="Address"> <Space.Compact> <Form.Item name={['address', 'province']} noStyle rules={[{ required: true, message: 'Province is required' }]} > <Select placeholder="Select province"> <Option value="Zhejiang">Zhejiang</Option> <Option value="Jiangsu">Jiangsu</Option> </Select> </Form.Item> <Form.Item name={['address', 'street']} noStyle rules={[{ required: true, message: 'Street is required' }]} > <Input style={{ width: '50%' }} placeholder="Input street" /> </Form.Item> </Space.Compact> </Form.Item> <Form.Item label="BirthDate" style={{ marginBottom: 0 }}> <Form.Item name="year" rules={[{ required: true }]} style={{ display: 'inline-block', width: 'calc(50% - 8px)' }} > <Input placeholder="Input birth year" /> </Form.Item> <Form.Item name="month" rules={[{ required: true }]} style={{ display: 'inline-block', width: 'calc(50% - 8px)', margin: '0 8px' }} > <Input placeholder="Input birth month" /> </Form.Item> </Form.Item> <Form.Item label={null}> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); export default App; ``` ### 自定义表单控件 自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件遵循以下的约定: > - 提供受控属性 `value` 或其它与 [`valuePropName`](#formitem) 的值同名的属性。 > - 提供 `onChange` 事件或 [`trigger`](#formitem) 的值同名的事件。 > - 转发 ref 或者传递 id 属性到 dom 以支持 `scrollToField` 方法。 ```tsx import React, { useState } from 'react'; import { Button, Form, Input, Select } from 'antd'; const { Option } = Select; type Currency = 'rmb' | 'dollar'; interface PriceValue { number?: number; currency?: Currency; } interface PriceInputProps { id?: string; value?: PriceValue; onChange?: (value: PriceValue) => void; } const PriceInput: React.FC<PriceInputProps> = (props) => { const { id, value = {}, onChange } = props; const [number, setNumber] = useState(0); const [currency, setCurrency] = useState<Currency>('rmb'); const triggerChange = (changedValue: { number?: number; currency?: Currency }) => { onChange?.({ number, currency, ...value, ...changedValue }); }; const onNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => { const newNumber = Number.parseInt(e.target.value || '0', 10); if (Number.isNaN(number)) { return; } if (!('number' in value)) { setNumber(newNumber); } triggerChange({ number: newNumber }); }; const onCurrencyChange = (newCurrency: Currency) => { if (!('currency' in value)) { setCurrency(newCurrency); } triggerChange({ currency: newCurrency }); }; return ( <span id={id}> <Input type="text" value={value.number || number} onChange={onNumberChange} style={{ width: 100 }} /> <Select value={value.currency || currency} style={{ width: 80, margin: '0 8px' }} onChange={onCurrencyChange} > <Option value="rmb">RMB</Option> <Option value="dollar">Dollar</Option> </Select> </span> ); }; const App: React.FC = () => { const onFinish = (values: any) => { console.log('Received values from form: ', values); }; const checkPrice = (_: any, value: { number: number }) => { if (value.number > 0) { return Promise.resolve(); } return Promise.reject(new Error('Price must be greater than zero!')); }; return ( <Form name="customized_form_controls" layout="inline" onFinish={onFinish} initialValues={{ price: { number: 0, currency: 'rmb', }, }} > <Form.Item name="price" label="Price" rules={[{ validator: checkPrice }]}> <PriceInput /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); }; export default App; ``` ### 表单数据存储于上层组件 通过 `onFieldsChange` 和 `fields`,可以把表单的数据存储到上层组件或者 [Redux](https://github.com/reactjs/redux)、[dva](https://github.com/dvajs/dva) 中,更多可参考 [rc-field-form 示例](https://rc-field-form.react-component.now.sh/?selectedKind=rc-field-form&selectedStory=StateForm-redux&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Factions%2Factions-panel)。 **注意:** 将表单数据存储于外部容器[并非好的实践](https://github.com/reduxjs/redux/issues/1287#issuecomment-175351978),如无必要请避免使用。 ```tsx import React, { useState } from 'react'; import { Form, Input, Typography } from 'antd'; const { Paragraph } = Typography; interface FieldData { name: string | number | (string | number)[]; value?: any; touched?: boolean; validating?: boolean; errors?: string[]; } interface CustomizedFormProps { onChange: (fields: FieldData[]) => void; fields: FieldData[]; } const CustomizedForm: React.FC<CustomizedFormProps> = ({ onChange, fields }) => ( <Form name="global_state" layout="inline" fields={fields} onFieldsChange={(_, allFields) => { onChange(allFields); }} > <Form.Item name="username" label="Username" rules={[{ required: true, message: 'Username is required!' }]} > <Input /> </Form.Item> </Form> ); const App: React.FC = () => { const [fields, setFields] = useState<FieldData[]>([{ name: ['username'], value: 'Ant Design' }]); return ( <> <CustomizedForm fields={fields} onChange={(newFields) => { setFields(newFields); }} /> <Paragraph style={{ maxWidth: 440, marginTop: 24 }}> <pre style={{ border: 'none' }}>{JSON.stringify(fields, null, 2)}</pre> </Paragraph> </> ); }; export default App; ``` ### 多表单联动 通过 `Form.Provider` 在表单间处理数据。本例子中,Modal 的确认按钮在 Form 之外,通过 `form.submit` 方法调用表单提交功能。反之,则推荐使用 `<Button htmlType="submit" />` 调用 web 原生提交逻辑。 ```tsx import React, { useEffect, useRef, useState } from 'react'; import { SmileOutlined, UserOutlined } from '@ant-design/icons'; import { Avatar, Button, Flex, Form, Input, InputNumber, Modal, Space, Typography } from 'antd'; import type { GetRef } from 'antd'; type FormInstance = GetRef<typeof Form>; const layout = { labelCol: { span: 8 }, wrapperCol: { span: 16 }, }; const tailLayout = { wrapperCol: { offset: 8, span: 16 }, }; interface UserType { name: string; age: string; } interface ModalFormProps { open: boolean; onCancel: () => void; } // reset form fields when modal is form, closed const useResetFormOnCloseModal = ({ form, open }: { form: FormInstance; open: boolean }) => { const prevOpenRef = useRef<boolean>(null); useEffect(() => { prevOpenRef.current = open; }, [open]); const prevOpen = prevOpenRef.current; useEffect(() => { if (!open && prevOpen) { form.resetFields(); } }, [form, prevOpen, open]); }; const ModalForm: React.FC<ModalFormProps> = ({ open, onCancel }) => { const [form] = Form.useForm(); useResetFormOnCloseModal({ form, open, }); const onOk = () => { form.submit(); }; return ( <Modal title="Basic Drawer" open={open} onOk={onOk} onCancel={onCancel}> <Form form={form} layout="vertical" name="userForm"> <Form.Item name="name" label="User Name" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item name="age" label="User Age" rules={[{ required: true }]}> <InputNumber /> </Form.Item> </Form> </Modal> ); }; const App: React.FC = () => { const [open, setOpen] = useState(false); const showUserModal = () => { setOpen(true); }; const hideUserModal = () => { setOpen(false); }; const onFinish = (values: any) => { console.log('Finish:', values); }; return ( <Form.Provider onFormFinish={(name, { values, forms }) => { if (name === 'userForm') { const { basicForm } = forms; const users = basicForm.getFieldValue('users') || []; basicForm.setFieldsValue({ users: [...users, values] }); setOpen(false); } }} > <Form {...layout} name="basicForm" onFinish={onFinish} style={{ maxWidth: 600 }}> <Form.Item name="group" label="Group Name" rules={[{ required: true }]}> <Input /> </Form.Item> {/* Create a hidden field to make Form instance record this */} <Form.Item name="users" noStyle /> <Form.Item label="User List" shouldUpdate={(prevValues, curValues) => prevValues.users !== curValues.users} > {({ getFieldValue }) => { const users: UserType[] = getFieldValue('users') || []; return users.length ? ( <Flex vertical gap={8}> {users.map((user) => ( <Space key={user.name}> <Avatar icon={<UserOutlined />} /> {`${user.name} - ${user.age}`} </Space> ))} </Flex> ) : ( <Typography.Text className="ant-form-text" type="secondary"> ( <SmileOutlined /> No user yet. ) </Typography.Text> ); }} </Form.Item> <Form.Item {...tailLayout}> <Button htmlType="submit" type="primary"> Submit </Button> <Button htmlType="button" style={{ margin: '0 8px' }} onClick={showUserModal}> Add User </Button> </Form.Item> </Form> <ModalForm open={open} onCancel={hideUserModal} /> </Form.Provider> ); }; export default App; ``` ### 内联登录栏 内联登录栏,常用在顶部导航栏中。 ```tsx import React, { useEffect, useState } from 'react'; import { LockOutlined, UserOutlined } from '@ant-design/icons'; import { Button, Form, Input } from 'antd'; const App: React.FC = () => { const [form] = Form.useForm(); const [clientReady, setClientReady] = useState<boolean>(false); // To disable submit button at the beginning. useEffect(() => { setClientReady(true); }, []); const onFinish = (values: any) => { console.log('Finish:', values); }; return ( <Form form={form} name="horizontal_login" layout="inline" onFinish={onFinish}> <Form.Item name="username" rules={[{ required: true, message: 'Please input your username!' }]} > <Input prefix={<UserOutlined />} placeholder="Username" /> </Form.Item> <Form.Item name="password" rules={[{ required: true, message: 'Please input your password!' }]} > <Input prefix={<LockOutlined />} type="password" placeholder="Password" /> </Form.Item> <Form.Item shouldUpdate> {() => ( <Button type="primary" htmlType="submit" disabled={ !clientReady || !form.isFieldsTouched(true) || !!form.getFieldsError().filter(({ errors }) => errors.length).length } > Log in </Button> )} </Form.Item> </Form> ); }; export default App; ``` ### 登录框 普通的登录框,可以容纳更多的元素。 > 🛎️ 想要 3 分钟实现登录表单?试试 [Pro Components](https://procomponents.ant.design/components/login-form)! ```tsx import React from 'react'; import { LockOutlined, UserOutlined } from '@ant-design/icons'; import { Button, Checkbox, Form, Input, Flex } from 'antd'; const App: React.FC = () => { const onFinish = (values: any) => { console.log('Received values of form: ', values); }; return ( <Form name="login" initialValues={{ remember: true }} style={{ maxWidth: 360 }} onFinish={onFinish} > <Form.Item name="username" rules={[{ required: true, message: 'Please input your Username!' }]} > <Input prefix={<UserOutlined />} placeholder="Username" /> </Form.Item> <Form.Item name="password" rules={[{ required: true, message: 'Please input your Password!' }]} > <Input prefix={<LockOutlined />} type="password" placeholder="Password" /> </Form.Item> <Form.Item> <Flex justify="space-between" align="center"> <Form.Item name="remember" valuePropName="checked" noStyle> <Checkbox>Remember me</Checkbox> </Form.Item> <a href="">Forgot password</a> </Flex> </Form.Item> <Form.Item> <Button block type="primary" htmlType="submit"> Log in </Button> or <a href="">Register now!</a> </Form.Item> </Form> ); }; export default App; ``` ### 注册新用户 用户填写必须的信息以注册新用户。 ```tsx import React, { useState } from 'react'; import type { CascaderProps, FormItemProps, FormProps } from 'antd'; import { AutoComplete, Button, Cascader, Checkbox, Col, Form, Input, InputNumber, Row, Select, } from 'antd'; import type { DefaultOptionType } from 'antd/es/select'; const { Option } = Select; interface FormCascaderOption { value: string; label: string; children?: FormCascaderOption[]; } const residences: CascaderProps<FormCascaderOption>['options'] = [ { value: 'zhejiang', label: 'Zhejiang', children: [ { value: 'hangzhou', label: 'Hangzhou', children: [ { value: 'xihu', label: 'West Lake', }, ], }, ], }, { value: 'jiangsu', label: 'Jiangsu', children: [ { value: 'nanjing', label: 'Nanjing', children: [ { value: 'zhonghuamen', label: 'Zhong Hua Men', }, ], }, ], }, ]; const formItemLayout: FormProps = { labelCol: { xs: { span: 24 }, sm: { span: 8 }, }, wrapperCol: { xs: { span: 24 }, sm: { span: 16 }, }, }; const tailFormItemLayout: FormItemProps = { wrapperCol: { xs: { span: 24, offset: 0, }, sm: { span: 16, offset: 8, }, }, }; const App: React.FC = () => { const [form] = Form.useForm(); const onFinish = (values: any) => { console.log('Received values of form: ', values); }; const prefixSelector = ( <Form.Item name="prefix" noStyle> <Select style={{ width: 70 }}> <Option value="86">+86</Option> <Option value="87">+87</Option> </Select> </Form.Item> ); const suffixSelector = ( <Form.Item name="suffix" noStyle> <Select style={{ width: 70 }}> <Option value="USD">$</Option> <Option value="CNY">¥</Option> </Select> </Form.Item> ); const [autoCompleteResult, setAutoCompleteResult] = useState<string[]>([]); const onWebsiteChange = (value: string) => { setAutoCompleteResult( value ? ['.com', '.org', '.net'].map((domain) => `${value}${domain}`) : [], ); }; const websiteOptions = autoCompleteResult.map<DefaultOptionType>((website) => ({ label: website, value: website, })); return ( <Form {...formItemLayout} form={form} name="register" onFinish={onFinish} initialValues={{ residence: ['zhejiang', 'hangzhou', 'xihu'], prefix: '86' }} style={{ maxWidth: 600 }} scrollToFirstError > <Form.Item name="email" label="E-mail" rules={[ { type: 'email', message: 'The input is not valid E-mail!', }, { required: true, message: 'Please input your E-mail!', }, ]} > <Input /> </Form.Item> <Form.Item name="password" label="Password" rules={[ { required: true, message: 'Please input your password!', }, ]} hasFeedback > <Input.Password /> </Form.Item> <Form.Item name="confirm" label="Confirm Password" dependencies={['password']} hasFeedback rules={[ { required: true, message: 'Please confirm your password!', }, ({ getFieldValue }) => ({ validator(_, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve(); } return Promise.reject(new Error('The new password that you entered do not match!')); }, }), ]} > <Input.Password /> </Form.Item> <Form.Item name="nickname" label="Nickname" tooltip="What do you want others to call you?" rules={[{ required: true, message: 'Please input your nickname!', whitespace: true }]} > <Input /> </Form.Item> <Form.Item name="residence" label="Habitual Residence" rules={[ { type: 'array', required: true, message: 'Please select your habitual residence!' }, ]} > <Cascader options={residences} /> </Form.Item> <Form.Item name="phone" label="Phone Number" rules={[{ required: true, message: 'Please input your phone number!' }]} > <Input addonBefore={prefixSelector} style={{ width: '100%' }} /> </Form.Item> <Form.Item name="donation" label="Donation" rules={[{ required: true, message: 'Please input donation amount!' }]} > <InputNumber addonAfter={suffixSelector} style={{ width: '100%' }} /> </Form.Item> <Form.Item name="website" label="Website" rules={[{ required: true, message: 'Please input website!' }]} > <AutoComplete options={websiteOptions} onChange={onWebsiteChange} placeholder="website"> <Input /> </AutoComplete> </Form.Item> <Form.Item name="intro" label="Intro" rules={[{ required: true, message: 'Please input Intro' }]} > <Input.TextArea showCount maxLength={100} /> </Form.Item> <Form.Item name="gender" label="Gender" rules={[{ required: true, message: 'Please select gender!' }]} > <Select placeholder="select your gender"> <Option value="male">Male</Option> <Option value="female">Female</Option> <Option value="other">Other</Option> </Select> </Form.Item> <Form.Item label="Captcha" extra="We must make sure that your are a human."> <Row gutter={8}> <Col span={12}> <Form.Item name="captcha" noStyle rules={[{ required: true, message: 'Please input the captcha you got!' }]} > <Input /> </Form.Item> </Col> <Col span={12}> <Button>Get captcha</Button> </Col> </Row> </Form.Item> <Form.Item name="agreement" valuePropName="checked" rules={[ { validator: (_, value) => value ? Promise.resolve() : Promise.reject(new Error('Should accept agreement')), }, ]} {...tailFormItemLayout} > <Checkbox> I have read the <a href="">agreement</a> </Checkbox> </Form.Item> <Form.Item {...tailFormItemLayout}> <Button type="primary" htmlType="submit"> Register </Button> </Form.Item> </Form> ); }; export default App; ``` ### 高级搜索 三列栅格式的表单排列方式,常用于数据表格的高级搜索。 有部分定制的样式代码,由于输入标签长度不确定,需要根据具体情况自行调整。 > 🛎️ 想要 3 分钟实现? 试试 ProForm 的[查询表单](https://procomponents.ant.design/components/form#%E6%9F%A5%E8%AF%A2%E7%AD%9B%E9%80%89)! ```tsx import React, { useState } from 'react'; import { DownOutlined } from '@ant-design/icons'; import { Button, Col, Form, Input, Row, Select, Space, theme } from 'antd'; const { Option } = Select; const AdvancedSearchForm = () => { const { token } = theme.useToken(); const [form] = Form.useForm(); const [expand, setExpand] = useState(false); const formStyle: React.CSSProperties = { maxWidth: 'none', background: token.colorFillAlter, borderRadius: token.borderRadiusLG, padding: 24, }; const getFields = () => { const count = expand ? 10 : 6; const children = []; for (let i = 0; i < count; i++) { children.push( <Col span={8} key={i}> {i % 3 !== 1 ? ( <Form.Item name={`field-${i}`} label={`Field ${i}`} rules={[ { required: true, message: 'Input something!', }, ]} > <Input placeholder="placeholder" /> </Form.Item> ) : ( <Form.Item name={`field-${i}`} label={`Field ${i}`} rules={[ { required: true, message: 'Select something!', }, ]} initialValue="1" > <Select> <Option value="1"> longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong </Option> <Option value="2">222</Option> </Select> </Form.Item> )} </Col>, ); } return children; }; const onFinish = (values: any) => { console.log('Received values of form: ', values); }; return ( <Form form={form} name="advanced_search" style={formStyle} onFinish={onFinish}> <Row gutter={24}>{getFields()}</Row> <div style={{ textAlign: 'right' }}> <Space size="small"> <Button type="primary" htmlType="submit"> Search </Button> <Button onClick={() => { form.resetFields(); }} > Clear </Button> <a style={{ fontSize: 12 }} onClick={() => { setExpand(!expand); }} > <DownOutlined rotate={expand ? 180 : 0} /> Collapse </a> </Space> </div> </Form> ); }; const App: React.FC = () => { const { token } = theme.useToken(); const listStyle: React.CSSProperties = { lineHeight: '200px', textAlign: 'center', background: token.colorFillAlter, borderRadius: token.borderRadiusLG, marginTop: 16, }; return ( <> <AdvancedSearchForm /> <div style={listStyle}>Search Result List</div> </> ); }; export default App; ``` ### 弹出层中的新建表单 当用户访问一个展示了某个列表的页面,想新建一项但又不想跳转页面时,可以用 Modal 弹出一个表单,用户填写必要信息后创建新的项。 > 🛎️ 想要 3 分钟实现?试试 ProForm 的 [Modal 表单](https://procomponents.ant.design/components/form#modal-%E8%A1%A8%E5%8D%95)! ```tsx import React, { useState } from 'react'; import { Button, Form, Input, Modal, Radio } from 'antd'; interface Values { title?: string; description?: string; modifier?: string; } const App: React.FC = () => { const [form] = Form.useForm(); const [formValues, setFormValues] = useState<Values>(); const [open, setOpen] = useState(false); const onCreate = (values: Values) => { console.log('Received values of form: ', values); setFormValues(values); setOpen(false); }; return ( <> <Button type="primary" onClick={() => setOpen(true)}> New Collection </Button> <pre>{JSON.stringify(formValues, null, 2)}</pre> <Modal open={open} title="Create a new collection" okText="Create" cancelText="Cancel" okButtonProps={{ autoFocus: true, htmlType: 'submit' }} onCancel={() => setOpen(false)} destroyOnHidden modalRender={(dom) => ( <Form layout="vertical" form={form} name="form_in_modal" initialValues={{ modifier: 'public' }} clearOnDestroy onFinish={(values) => onCreate(values)} > {dom} </Form> )} > <Form.Item name="title" label="Title" rules={[{ required: true, message: 'Please input the title of collection!' }]} > <Input /> </Form.Item> <Form.Item name="description" label="Description"> <Input type="textarea" /> </Form.Item> <Form.Item name="modifier" className="collection-create-form_last-form-item"> <Radio.Group> <Radio value="public">Public</Radio> <Radio value="private">Private</Radio> </Radio.Group> </Form.Item> </Modal> </> ); }; export default App; ``` ### 时间类控件 时间类组件的 `value` 类型为 `dayjs` 对象,所以在提交服务器前需要预处理。 ```tsx import React from 'react'; import { Button, DatePicker, Form, TimePicker } from 'antd'; const { RangePicker } = DatePicker; const formItemLayout = { labelCol: { xs: { span: 24 }, sm: { span: 8 }, }, wrapperCol: { xs: { span: 24 }, sm: { span: 16 }, }, }; const config = { rules: [{ type: 'object' as const, required: true, message: 'Please select time!' }], }; const rangeConfig = { rules: [{ type: 'array' as const, required: true, message: 'Please select time!' }], }; const onFinish = (fieldsValue: any) => { // Should format date value before submit. const rangeValue = fieldsValue['range-picker']; const rangeTimeValue = fieldsValue['range-time-picker']; const values = { ...fieldsValue, 'date-picker': fieldsValue['date-picker'].format('YYYY-MM-DD'), 'date-time-picker': fieldsValue['date-time-picker'].format('YYYY-MM-DD HH:mm:ss'), 'month-picker': fieldsValue['month-picker'].format('YYYY-MM'), 'range-picker': [rangeValue[0].format('YYYY-MM-DD'), rangeValue[1].format('YYYY-MM-DD')], 'range-time-picker': [ rangeTimeValue[0].format('YYYY-MM-DD HH:mm:ss'), rangeTimeValue[1].format('YYYY-MM-DD HH:mm:ss'), ], 'time-picker': fieldsValue['time-picker'].format('HH:mm:ss'), }; console.log('Received values of form: ', values); }; const App: React.FC = () => ( <Form name="time_related_controls" {...formItemLayout} onFinish={onFinish} style={{ maxWidth: 600 }} > <Form.Item name="date-picker" label="DatePicker" {...config}> <DatePicker /> </Form.Item> <Form.Item name="date-time-picker" label="DatePicker[showTime]" {...config}> <DatePicker showTime format="YYYY-MM-DD HH:mm:ss" /> </Form.Item> <Form.Item name="month-picker" label="MonthPicker" {...config}> <DatePicker picker="month" /> </Form.Item> <Form.Item name="range-picker" label="RangePicker" {...rangeConfig}> <RangePicker /> </Form.Item> <Form.Item name="range-time-picker" label="RangePicker[showTime]" {...rangeConfig}> <RangePicker showTime format="YYYY-MM-DD HH:mm:ss" /> </Form.Item> <Form.Item name="time-picker" label="TimePicker" {...config}> <TimePicker /> </Form.Item> <Form.Item label={null}> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); export default App; ``` ### 自行处理表单数据 `Form` 具有自动收集数据并校验的功能,但如果您不需要这个功能,或者默认的行为无法满足业务需求,可以选择自行处理数据。 ```tsx import React, { useState } from 'react'; import type { InputNumberProps } from 'antd'; import { Form, InputNumber } from 'antd'; type ValidateStatus = Parameters<typeof Form.Item>[0]['validateStatus']; const validatePrimeNumber = ( number: number, ): { validateStatus: ValidateStatus; errorMsg: string | null; } => { if (number === 11) { return { validateStatus: 'success', errorMsg: null, }; } return { validateStatus: 'error', errorMsg: 'The prime between 8 and 12 is 11!', }; }; const formItemLayout = { labelCol: { span: 7 }, wrapperCol: { span: 12 }, }; const tips = 'A prime is a natural number greater than 1 that has no positive divisors other than 1 and itself.'; const App: React.FC = () => { const [number, setNumber] = useState<{ value: number; validateStatus?: ValidateStatus; errorMsg?: string | null; }>({ value: 11 }); const onNumberChange: InputNumberProps['onChange'] = (value) => { setNumber({ ...validatePrimeNumber(value as number), value: value as number, }); }; return ( <Form style={{ maxWidth: 600 }}> <Form.Item {...formItemLayout} label="Prime between 8 & 12" validateStatus={number.validateStatus} help={number.errorMsg || tips} > <InputNumber min={8} max={12} value={number.value} onChange={onNumberChange} /> </Form.Item> </Form> ); }; export default App; ``` ### 自定义校验 我们提供了 `validateStatus` `help` `hasFeedback` 等属性,你可以不通过 Form 自己定义校验的时机和内容。 1. `validateStatus`: 校验状态,可选 'success', 'warning', 'error', 'validating'。 2. `hasFeedback`:用于给输入框添加反馈图标。 3. `help`:设置校验文案。 ```tsx import React from 'react'; import { SmileOutlined } from '@ant-design/icons'; import { Cascader, DatePicker, Form, Input, InputNumber, Mentions, Select, TimePicker, TreeSelect, } from 'antd'; const { Option } = Select; const formItemLayout = { labelCol: { xs: { span: 24 }, sm: { span: 6 }, }, wrapperCol: { xs: { span: 24 }, sm: { span: 14 }, }, }; const App: React.FC = () => ( <Form {...formItemLayout} style={{ maxWidth: 600 }}> <Form.Item label="Fail" validateStatus="error" help="Should be combination of numbers & alphabets" > <Input placeholder="unavailable choice" id="error" /> </Form.Item> <Form.Item label="Warning" validateStatus="warning"> <Input placeholder="Warning" id="warning" prefix={<SmileOutlined />} /> </Form.Item> <Form.Item label="Validating" hasFeedback validateStatus="validating" help="The information is being validated..." > <Input placeholder="I'm the content is being validated" id="validating" /> </Form.Item> <Form.Item label="Success" hasFeedback validateStatus="success"> <Input placeholder="I'm the content" id="success" /> </Form.Item> <Form.Item label="Warning" hasFeedback validateStatus="warning"> <Input placeholder="Warning" id="warning2" /> </Form.Item> <Form.Item label="Fail" hasFeedback validateStatus="error" help="Should be combination of numbers & alphabets" > <Input placeholder="unavailable choice" id="error2" /> </Form.Item> <Form.Item label="Success" hasFeedback validateStatus="success"> <DatePicker style={{ width: '100%' }} /> </Form.Item> <Form.Item label="Warning" hasFeedback validateStatus="warning"> <TimePicker style={{ width: '100%' }} /> </Form.Item> <Form.Item label="Error" hasFeedback validateStatus="error"> <DatePicker.RangePicker style={{ width: '100%' }} /> </Form.Item> <Form.Item label="Error" hasFeedback validateStatus="error"> <Select placeholder="I'm Select" allowClear> <Option value="1">Option 1</Option> <Option value="2">Option 2</Option> <Option value="3">Option 3</Option> </Select> </Form.Item> <Form.Item label="Validating" hasFeedback validateStatus="error" help="Something breaks the rule." > <Cascader placeholder="I'm Cascader" options={[{ value: 'xx', label: 'xx' }]} allowClear /> </Form.Item> <Form.Item label="Warning" hasFeedback validateStatus="warning" help="Need to be checked"> <TreeSelect placeholder="I'm TreeSelect" treeData={[{ value: 'xx', label: 'xx' }]} allowClear /> </Form.Item> <Form.Item label="inline" style={{ marginBottom: 0 }}> <Form.Item validateStatus="error" help="Please select right date" style={{ display: 'inline-block', width: 'calc(50% - 12px)' }} > <DatePicker /> </Form.Item> <span style={{ display: 'inline-block', width: '24px', lineHeight: '32px', textAlign: 'center' }} > - </span> <Form.Item style={{ display: 'inline-block', width: 'calc(50% - 12px)' }}> <DatePicker /> </Form.Item> </Form.Item> <Form.Item label="Success" hasFeedback validateStatus="success"> <InputNumber style={{ width: '100%' }} /> </Form.Item> <Form.Item label="Success" hasFeedback validateStatus="success"> <Input allowClear placeholder="with allowClear" /> </Form.Item> <Form.Item label="Warning" hasFeedback validateStatus="warning"> <Input.Password placeholder="with input password" /> </Form.Item> <Form.Item label="Error" hasFeedback validateStatus="error"> <Input.Password allowClear placeholder="with input password and allowClear" /> </Form.Item> <Form.Item label="Success" hasFeedback validateStatus="success"> <Input.OTP /> </Form.Item> <Form.Item label="Warning" hasFeedback validateStatus="warning"> <Input.OTP /> </Form.Item> <Form.Item label="Error" hasFeedback validateStatus="error"> <Input.OTP /> </Form.Item> <Form.Item label="Fail" validateStatus="error" hasFeedback> <Mentions /> </Form.Item> <Form.Item label="Fail" validateStatus="error" hasFeedback help="Should have something"> <Input.TextArea allowClear showCount /> </Form.Item> </Form> ); export default App; ``` ### 动态校验规则 根据不同情况执行不同的校验规则。 ```tsx import React, { useEffect, useState } from 'react'; import { Button, Checkbox, Form, Input } from 'antd'; const formItemLayout = { labelCol: { span: 4 }, wrapperCol: { span: 8 }, }; const formTailLayout = { labelCol: { span: 4 }, wrapperCol: { span: 8, offset: 4 }, }; const App: React.FC = () => { const [form] = Form.useForm(); const [checkNick, setCheckNick] = useState(false); useEffect(() => { form.validateFields(['nickname']); }, [checkNick, form]); const onCheckboxChange = (e: { target: { checked: boolean } }) => { setCheckNick(e.target.checked); }; const onCheck = async () => { try { const values = await form.validateFields(); console.log('Success:', values); } catch (errorInfo) { console.log('Failed:', errorInfo); } }; return ( <Form form={form} name="dynamic_rule" style={{ maxWidth: 600 }}> <Form.Item {...formItemLayout} name="username" label="Name" rules={[{ required: true, message: 'Please input your name' }]} > <Input placeholder="Please input your name" /> </Form.Item> <Form.Item {...formItemLayout} name="nickname" label="Nickname" rules={[{ required: checkNick, message: 'Please input your nickname' }]} > <Input placeholder="Please input your nickname" /> </Form.Item> <Form.Item {...formTailLayout}> <Checkbox checked={checkNick} onChange={onCheckboxChange}> Nickname is required </Checkbox> </Form.Item> <Form.Item {...formTailLayout}> <Button type="primary" onClick={onCheck}> Check </Button> </Form.Item> </Form> ); }; export default App; ``` ### 校验与更新依赖 Form.Item 可以通过 `dependencies` 属性,设置关联字段。当关联字段的值发生变化时,会触发校验与更新。 ```tsx import React from 'react'; import { Alert, Form, Input, Typography } from 'antd'; const App: React.FC = () => { const [form] = Form.useForm(); return ( <Form form={form} name="dependencies" autoComplete="off" style={{ maxWidth: 600 }} layout="vertical" > <Alert message=" Try modify `Password2` and then modify `Password`" type="info" showIcon /> <Form.Item label="Password" name="password" rules={[{ required: true }]}> <Input /> </Form.Item> {/* Field */} <Form.Item label="Confirm Password" name="password2" dependencies={['password']} rules={[ { required: true, }, ({ getFieldValue }) => ({ validator(_, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve(); } return Promise.reject(new Error('The new password that you entered do not match!')); }, }), ]} > <Input /> </Form.Item> {/* Render Props */} <Form.Item noStyle dependencies={['password2']}> {() => ( <Typography> <p> Only Update when <code>password2</code> updated: </p> <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre> </Typography> )} </Form.Item> </Form> ); }; export default App; ``` ### 滑动到错误字段 校验失败时/手动滚动到错误字段。 ```tsx import React from 'react'; import { Button, Flex, Form, Input, Select } from 'antd'; const App = () => { const [form] = Form.useForm(); return ( <Form form={form} scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }} style={{ paddingBlock: 32 }} labelCol={{ span: 6 }} wrapperCol={{ span: 14 }} > <Form.Item wrapperCol={{ offset: 6 }}> <Button onClick={() => form.scrollToField('bio')}>Scroll to Bio</Button> </Form.Item> <Form.Item name="username" label="UserName" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item label="Occupation" name="occupation"> <Select options={[ { label: 'Designer', value: 'designer' }, { label: 'Developer', value: 'developer' }, { label: 'Product Manager', value: 'product-manager' }, ]} /> </Form.Item> <Form.Item name="motto" label="Motto"> <Input.TextArea rows={4} /> </Form.Item> <Form.Item name="bio" label="Bio" rules={[{ required: true }]}> <Input.TextArea rows={6} /> </Form.Item> <Form.Item wrapperCol={{ offset: 6 }}> <Flex gap="small"> <Button type="primary" htmlType="submit"> Submit </Button> <Button danger onClick={() => form.resetFields()}> Reset </Button> </Flex> </Form.Item> </Form> ); }; export default App; ``` ### 校验其他组件 以上演示没有出现的表单控件对应的校验演示。 ```tsx import React from 'react'; import { InboxOutlined, UploadOutlined } from '@ant-design/icons'; import { Button, Checkbox, Col, ColorPicker, Form, InputNumber, Radio, Rate, Row, Select, Slider, Space, Switch, Upload, } from 'antd'; const { Option } = Select; const formItemLayout = { labelCol: { span: 6 }, wrapperCol: { span: 14 }, }; const normFile = (e: any) => { console.log('Upload event:', e); if (Array.isArray(e)) { return e; } return e?.fileList; }; const onFinish = (values: any) => { console.log('Received values of form: ', values); }; const App: React.FC = () => ( <Form name="validate_other" {...formItemLayout} onFinish={onFinish} initialValues={{ 'input-number': 3, 'checkbox-group': ['A', 'B'], rate: 3.5, 'color-picker': null, }} style={{ maxWidth: 600 }} > <Form.Item label="Plain Text"> <span className="ant-form-text">China</span> </Form.Item> <Form.Item name="select" label="Select" hasFeedback rules={[{ required: true, message: 'Please select your country!' }]} > <Select placeholder="Please select a country"> <Option value="china">China</Option> <Option value="usa">U.S.A</Option> </Select> </Form.Item> <Form.Item name="select-multiple" label="Select[multiple]" rules={[{ required: true, message: 'Please select your favourite colors!', type: 'array' }]} > <Select mode="multiple" placeholder="Please select favourite colors"> <Option value="red">Red</Option> <Option value="green">Green</Option> <Option value="blue">Blue</Option> </Select> </Form.Item> <Form.Item label="InputNumber"> <Form.Item name="input-number" noStyle> <InputNumber min={1} max={10} /> </Form.Item> <span className="ant-form-text" style={{ marginInlineStart: 8 }}> machines </span> </Form.Item> <Form.Item name="switch" label="Switch" valuePropName="checked"> <Switch /> </Form.Item> <Form.Item name="slider" label="Slider"> <Slider marks={{ 0: 'A', 20: 'B', 40: 'C', 60: 'D', 80: 'E', 100: 'F', }} /> </Form.Item> <Form.Item name="radio-group" label="Radio.Group"> <Radio.Group> <Radio value="a">item 1</Radio> <Radio value="b">item 2</Radio> <Radio value="c">item 3</Radio> </Radio.Group> </Form.Item> <Form.Item name="radio-button" label="Radio.Button" rules={[{ required: true, message: 'Please pick an item!' }]} > <Radio.Group> <Radio.Button value="a">item 1</Radio.Button> <Radio.Button value="b">item 2</Radio.Button> <Radio.Button value="c">item 3</Radio.Button> </Radio.Group> </Form.Item> <Form.Item name="checkbox-group" label="Checkbox.Group"> <Checkbox.Group> <Row> <Col span={8}> <Checkbox value="A" style={{ lineHeight: '32px' }}> A </Checkbox> </Col> <Col span={8}> <Checkbox value="B" style={{ lineHeight: '32px' }} disabled> B </Checkbox> </Col> <Col span={8}> <Checkbox value="C" style={{ lineHeight: '32px' }}> C </Checkbox> </Col> <Col span={8}> <Checkbox value="D" style={{ lineHeight: '32px' }}> D </Checkbox> </Col> <Col span={8}> <Checkbox value="E" style={{ lineHeight: '32px' }}> E </Checkbox> </Col> <Col span={8}> <Checkbox value="F" style={{ lineHeight: '32px' }}> F </Checkbox> </Col> </Row> </Checkbox.Group> </Form.Item> <Form.Item name="rate" label="Rate"> <Rate /> </Form.Item> <Form.Item name="upload" label="Upload" valuePropName="fileList" getValueFromEvent={normFile} extra="longgggggggggggggggggggggggggggggggggg" > <Upload name="logo" action="/upload.do" listType="picture"> <Button icon={<UploadOutlined />}>Click to upload</Button> </Upload> </Form.Item> <Form.Item label="Dragger"> <Form.Item name="dragger" valuePropName="fileList" getValueFromEvent={normFile} noStyle> <Upload.Dragger name="files" action="/upload.do"> <p className="ant-upload-drag-icon"> <InboxOutlined /> </p> <p className="ant-upload-text">Click or drag file to this area to upload</p> <p className="ant-upload-hint">Support for a single or bulk upload.</p> </Upload.Dragger> </Form.Item> </Form.Item> <Form.Item name="color-picker" label="ColorPicker" rules={[{ required: true, message: 'color is required!' }]} > <ColorPicker /> </Form.Item> <Form.Item wrapperCol={{ span: 12, offset: 6 }}> <Space> <Button type="primary" htmlType="submit"> Submit </Button> <Button htmlType="reset">reset</Button> </Space> </Form.Item> </Form> ); export default App; ``` ### getValueProps + normalize 配合 `getValueProps` 和 `normalize`,可以转换 `value` 的格式,如将时间戳转成 `dayjs` 对象再传给 `DatePicker`。 ```tsx import React from 'react'; import type { FormProps } from 'antd'; import { Button, DatePicker, Form } from 'antd'; import dayjs from 'dayjs'; const dateTimestamp = dayjs('2024-01-01').valueOf(); type FieldType = { date?: string; }; const onFinish: FormProps<FieldType>['onFinish'] = (values) => { console.log('Success:', values); }; const App: React.FC = () => ( <Form name="getValueProps" labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }} initialValues={{ date: dateTimestamp }} onFinish={onFinish} autoComplete="off" > <Form.Item<FieldType> label="Date" name="date" rules={[{ required: true }]} getValueProps={(value) => ({ value: value && dayjs(Number(value)) })} normalize={(value) => value && `${dayjs(value).valueOf()}`} > <DatePicker /> </Form.Item> <Form.Item label={null}> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); export default App; ``` ### Disabled Input Debug Test disabled Input with validate state ```tsx import React from 'react'; import { Form, Input } from 'antd'; const App: React.FC = () => ( <Form style={{ maxWidth: 600 }}> <Form.Item label="Normal0"> <Input placeholder="unavailable choice" value="Buggy!" /> </Form.Item> <Form.Item label="Fail0" validateStatus="error" help="Buggy!"> <Input placeholder="unavailable choice" value="Buggy!" /> </Form.Item> <Form.Item label="FailDisabled0" validateStatus="error" help="Buggy!"> <Input placeholder="unavailable choice" disabled value="Buggy!" /> </Form.Item> <Form.Item label="Normal1"> <Input placeholder="unavailable choice" value="Buggy!" /> </Form.Item> <Form.Item label="Fail1" validateStatus="error" help="Buggy!"> <Input placeholder="unavailable choice" value="Buggy!" /> </Form.Item> <Form.Item label="FailDisabled1" validateStatus="error" help="Buggy!"> <Input placeholder="unavailable choice" disabled value="Buggy!" /> </Form.Item> <Form.Item label="Normal2"> <Input placeholder="unavailable choice" addonBefore="Buggy!" /> </Form.Item> <Form.Item label="Fail2" validateStatus="error" help="Buggy!"> <Input placeholder="unavailable choice" addonBefore="Buggy!" /> </Form.Item> <Form.Item label="FailDisabled2" validateStatus="error" help="Buggy!"> <Input placeholder="unavailable choice" disabled addonBefore="Buggy!" /> </Form.Item> <Form.Item label="Normal3"> <Input placeholder="unavailable choice" prefix="人民币" value="50" /> </Form.Item> <Form.Item label="Fail3" validateStatus="error" help="Buggy!"> <Input placeholder="unavailable choice" prefix="人民币" value="50" /> </Form.Item> <Form.Item label="FailDisabled3" validateStatus="error" help="Buggy!"> <Input placeholder="unavailable choice" disabled prefix="人民币" value="50" /> </Form.Item> </Form> ); export default App; ``` ### 测试 label 省略 `label` 中使用 `<Typography.Text ellipsis>` 时应该显示 `...`。 ```tsx import React from 'react'; import { Form, Input, Typography } from 'antd'; const App: React.FC = () => ( <Form name="label-ellipsis" labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }} > <Form.Item label={ <Typography.Text ellipsis> longtextlongtextlongtextlongtextlongtextlongtextlongtext </Typography.Text> } name="username" > <Input /> </Form.Item> <Form.Item label={ <Typography.Text ellipsis> longtext longtext longtext longtext longtext longtext longtext </Typography.Text> } name="password" > <Input.Password /> </Form.Item> </Form> ); export default App; ``` ### 测试特殊 col 24 用法 See issue [#32980](https://github.com/ant-design/ant-design/issues/32980). ```tsx import React from 'react'; import { Button, Divider, Form, Input, Select } from 'antd'; const sharedItem = ( <Form.Item label={ <a href="https://github.com/ant-design/ant-design/issues/36459" target="_blank" rel="noreferrer" > #36459 </a> } initialValue={['bamboo']} name="select" style={{ boxShadow: '0 0 3px red' }} > <Select style={{ width: '70%' }} mode="multiple" options={[ { label: 'Bamboo', value: 'bamboo' }, { label: 'Little', value: 'little' }, { label: 'Light', value: 'light' }, ]} /> </Form.Item> ); const App: React.FC = () => { const onFinish = (values: any) => { console.log('Success:', values); }; const onFinishFailed = (errorInfo: any) => { console.log('Failed:', errorInfo); }; return ( <> <Form name="col-24-debug" labelCol={{ span: 24 }} wrapperCol={{ span: 24 }} initialValues={{ remember: true }} onFinish={onFinish} onFinishFailed={onFinishFailed} style={{ maxWidth: 600 }} autoComplete="off" > <Form.Item label="Username" name="username" rules={[{ required: true, message: 'Please input your username!' }]} > <Input /> </Form.Item> <Form.Item label="Password" name="password" rules={[{ required: true, message: 'Please input your password!' }]} > <Input.Password /> </Form.Item> {sharedItem} <Form.Item> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> <Form name="responsive" labelCol={{ sm: 24, xl: 24 }} wrapperCol={{ sm: 24, xl: 24 }} initialValues={{ remember: true }} onFinish={onFinish} onFinishFailed={onFinishFailed} autoComplete="off" > <Form.Item label="Username" name="username" rules={[{ required: true, message: 'Please input your username!' }]} > <Input /> </Form.Item> <Form.Item label="Password" name="password" rules={[{ required: true, message: 'Please input your password!' }]} > <Input.Password /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> <Divider /> <Form layout="vertical"> {sharedItem} <Form.Item label="col12" name="col12" labelCol={{ span: 12 }} wrapperCol={{ span: 12 }}> <Input /> </Form.Item> </Form> </> ); }; export default App; ``` ### 引用字段 请优先使用 `ref`! ```tsx import React from 'react'; import type { InputRef } from 'antd'; import { Button, Form, Input } from 'antd'; const App: React.FC = () => { const [form] = Form.useForm(); const ref = React.useRef<InputRef>(null); return ( <Form form={form} initialValues={{ list: ['light'] }} style={{ maxWidth: 600 }}> <Form.Item name="test" label="test"> <Input ref={ref} /> </Form.Item> <Form.List name="list"> {(fields) => fields.map((field) => ( <Form.Item {...field} key={field.key}> <Input ref={ref} /> </Form.Item> )) } </Form.List> <Button htmlType="button" onClick={() => { form.getFieldInstance('test').focus(); }} > Focus Form.Item </Button> <Button onClick={() => { form.getFieldInstance(['list', 0]).focus(); }} > Focus Form.List </Button> </Form> ); }; export default App; ``` ### Custom feedback icons 自定义反馈图标可以通过 `hasFeedback={{ icons: ... }}` 或 `<Form FeedbackIcons={icons}>` 传递(`Form.Item` 必须具有 `hasFeedback` 属性)。 ```tsx import React from 'react'; import { AlertFilled, CloseSquareFilled } from '@ant-design/icons'; import { Button, Form, Input, Tooltip, Mentions } from 'antd'; import { createStyles, css } from 'antd-style'; import uniqueId from 'lodash/uniqueId'; const useStyle = createStyles(() => ({ 'custom-feedback-icons': css` .ant-form-item-feedback-icon { pointer-events: all; } `, })); const App: React.FC = () => { const [form] = Form.useForm(); const { styles } = useStyle(); return ( <Form name="custom-feedback-icons" form={form} style={{ maxWidth: 600 }} feedbackIcons={({ errors }) => ({ error: ( <Tooltip key="tooltipKey" title={errors?.map((error) => <div key={uniqueId()}>{error}</div>)} color="red" > <CloseSquareFilled /> </Tooltip> ), })} > <Form.Item name="custom-feedback-test-item" label="Test" className={styles['custom-feedback-icons']} rules={[{ required: true, type: 'email' }, { min: 10 }]} help="" hasFeedback > <Input /> </Form.Item> <Form.Item name="custom-feedback-test-item2" label="Test" className={styles['custom-feedback-icons']} rules={[{ required: true, type: 'email' }, { min: 10 }]} help="" hasFeedback={{ icons: ({ errors }) => ({ error: ( <Tooltip key="tooltipKey" title={errors?.map((error) => <div key={uniqueId()}>{error}</div>)} color="pink" > <AlertFilled /> </Tooltip> ), success: false, }), }} > <Input /> </Form.Item> <Form.Item name="custom-feedback-test-item3" label="Test" className={styles['custom-feedback-icons']} hasFeedback validateStatus="success" initialValue="@mention1" > <Mentions allowClear options={[ { value: 'mention1', label: 'mention1', }, { value: 'mention2', label: 'mention2', }, ]} /> </Form.Item> <Form.Item> <Button htmlType="submit">Submit</Button> </Form.Item> </Form> ); }; export default App; ``` ### 组件 Token Component Token Debug. ```tsx import React from 'react'; import { ConfigProvider, Form, Input } from 'antd'; const App: React.FC = () => ( <ConfigProvider theme={{ components: { Form: { labelRequiredMarkColor: 'pink', labelColor: 'green', labelFontSize: 16, labelHeight: 34, labelColonMarginInlineStart: 4, labelColonMarginInlineEnd: 12, itemMarginBottom: 18, inlineItemMarginBottom: 18, }, }, }} > <Form name="component-token" labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }} initialValues={{ remember: true }} autoComplete="off" > <Form.Item label="Username" name="username" rules={[{ required: true, message: 'Please input your username!' }]} > <Input /> </Form.Item> <Form.Item label="Password" name="password" rules={[{ required: true, message: 'Please input your password!' }]} > <Input.Password /> </Form.Item> </Form> </ConfigProvider> ); export default App; ```

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zhixiaoqiang/antd-components-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server