Form表单
高性能表单控件,自带数据域管理。包含数据录入、校验以及对应样式。
何时使用
用于创建一个实体或收集信息。
需要对输入的数据类型进行校验时。
代码演示
基本的表单数据域控制展示,包含布局、初始化、验证、提交。
TypeScript
JavaScript
import { Form, Input, Button, Checkbox } from 'antd';
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
const tailLayout = {
wrapperCol: { offset: 8, span: 16 },
};
const Demo = () => {
const onFinish = values => {
console.log('Success:', values);
};
const onFinishFailed = errorInfo => {
console.log('Failed:', errorInfo);
};
return (
<Form
{...layout}
name="basic"
initialValues={{ remember: true }}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
>
<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 {...tailLayout} name="remember" valuePropName="checked">
<Checkbox>Remember me</Checkbox>
</Form.Item>
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
ReactDOM.render(<Demo />, mountNode);
通过 Form.useForm
对表单数据域进行交互。
TypeScript
JavaScript
import { Form, Input, Button, Select } from 'antd';
const { Option } = Select;
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
const tailLayout = {
wrapperCol: { offset: 8, span: 16 },
};
const Demo = () => {
const [form] = Form.useForm();
const onGenderChange = value => {
form.setFieldsValue({
note: `Hi, ${value === 'male' ? 'man' : 'lady'}!`,
});
};
const onFinish = values => {
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}>
<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 }) => {
return getFieldValue('gender') === 'other' ? (
<Form.Item name="customizeGender" label="Customize Gender" rules={[{ required: true }]}>
<Input />
</Form.Item>
) : null;
}}
</Form.Item>
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
Submit
</Button>
<Button htmlType="button" onClick={onReset}>
Reset
</Button>
<Button type="link" htmlType="button" onClick={onFill}>
Fill form
</Button>
</Form.Item>
</Form>
);
};
ReactDOM.render(<Demo />, mountNode);
#components-form-demo-control-hooks .ant-btn {
margin-right: 8px;
}
我们推荐使用 Form.useForm
创建表单数据域进行控制。如果是在 class component 下,你也可以通过 ref
获取数据域。
TypeScript
JavaScript
import { Form, Input, Button, Select } from 'antd';
const { Option } = Select;
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
const tailLayout = {
wrapperCol: { offset: 8, span: 16 },
};
class Demo extends React.Component {
formRef = React.createRef();
onGenderChange = value => {
this.formRef.current.setFieldsValue({
note: `Hi, ${value === 'male' ? 'man' : 'lady'}!`,
});
};
onFinish = values => {
console.log(values);
};
onReset = () => {
this.formRef.current.resetFields();
};
onFill = () => {
this.formRef.current.setFieldsValue({
note: 'Hello world!',
gender: 'male',
});
};
render() {
return (
<Form {...layout} ref={this.formRef} name="control-ref" onFinish={this.onFinish}>
<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={this.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 }) => {
return getFieldValue('gender') === 'other' ? (
<Form.Item
name="customizeGender"
label="Customize Gender"
rules={[{ required: true }]}
>
<Input />
</Form.Item>
) : null;
}}
</Form.Item>
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
Submit
</Button>
<Button htmlType="button" onClick={this.onReset}>
Reset
</Button>
<Button type="link" htmlType="button" onClick={this.onFill}>
Fill form
</Button>
</Form.Item>
</Form>
);
}
}
ReactDOM.render(<Demo />, mountNode);
#components-form-demo-control-ref .ant-btn {
margin-right: 8px;
}
表单有三种布局。
TypeScript
JavaScript
import React, { useState } from 'react';
import { Form, Input, Button, Radio } from 'antd';
const FormLayoutDemo = () => {
const [form] = Form.useForm();
const [formLayout, setFormLayout] = useState('horizontal');
const onFormLayoutChange = ({ layout }) => {
setFormLayout(layout);
};
const formItemLayout =
formLayout === 'horizontal'
? {
labelCol: { span: 4 },
wrapperCol: { span: 14 },
}
: null;
const buttonItemLayout =
formLayout === 'horizontal'
? {
wrapperCol: { span: 14, offset: 4 },
}
: null;
return (
<div>
<Form
{...formItemLayout}
layout={formLayout}
form={form}
initialValues={{ layout: formLayout }}
onValuesChange={onFormLayoutChange}
>
<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 {...buttonItemLayout}>
<Button type="primary">Submit</Button>
</Form.Item>
</Form>
</div>
);
};
ReactDOM.render(<FormLayoutDemo />, mountNode);
设置表单组件尺寸,仅对 antd 组件有效。
TypeScript
JavaScript
import React, { useState } from 'react';
import {
Form,
Input,
Button,
Radio,
Select,
Cascader,
DatePicker,
InputNumber,
TreeSelect,
Switch,
} from 'antd';
const FormSizeDemo = () => {
const [componentSize, setComponentSize] = useState('small');
const onFormLayoutChange = ({ size }) => {
setComponentSize(size);
};
return (
<div>
<Form
labelCol={{ span: 4 }}
wrapperCol={{ span: 14 }}
layout="horizontal"
initialValues={{ size: componentSize }}
onValuesChange={onFormLayoutChange}
size={componentSize}
>
<Form.Item label="Form Size" name="size">
<Radio.Group>
<Radio.Button value="small">Small</Radio.Button>
<Radio.Button value="middle">Middle</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">
<Switch />
</Form.Item>
<Form.Item label="Button">
<Button>Button</Button>
</Form.Item>
</Form>
</div>
);
};
ReactDOM.render(<FormSizeDemo />, mountNode);
动态增加、减少表单项。
import { Form, Input, Button } from 'antd';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
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 DynamicFieldSet = () => {
const onFinish = values => {
console.log('Received values of form:', values);
};
return (
<Form name="dynamic_form_item" {...formItemLayoutWithOutLabel} onFinish={onFinish}>
<Form.List name="names">
{(fields, { add, remove }) => {
return (
<div>
{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%', marginRight: 8 }} />
</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%' }}
>
<PlusOutlined /> Add field
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
ReactDOM.render(<DynamicFieldSet />, mountNode);
.dynamic-delete-button {
cursor: pointer;
position: relative;
top: 4px;
font-size: 24px;
color: #999;
transition: all 0.3s;
}
.dynamic-delete-button:hover {
color: #777;
}
.dynamic-delete-button[disabled] {
cursor: not-allowed;
opacity: 0.5;
}
name
属性支持嵌套数据结构。通过 validateMessages
或 message
自定义校验信息模板,模板内容可参考此处。
TypeScript
JavaScript
import { Form, Input, InputNumber, Button } from 'antd';
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
const validateMessages = {
required: 'This field is required!',
types: {
email: 'Not a validate email!',
number: 'Not a validate number!',
},
number: {
range: 'Must be between ${min} and ${max}',
},
};
const Demo = () => {
const onFinish = values => {
console.log(values);
};
return (
<Form {...layout} name="nest-messages" onFinish={onFinish} 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 wrapperCol={{ ...layout.wrapperCol, offset: 8 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
ReactDOM.render(<Demo />, mountNode);
这里演示 Form.Item
内有多个元素的使用方式。<Form.Item name="field" />
只会对它的直接子元素绑定表单功能,例如直接包裹了 Input/Select。如果控件前后还有一些文案或样式装点,或者一个表单项内有多个控件,你可以使用内嵌的 Form.Item
完成。你可以给 Form.Item
自定义 style
进行内联布局,或者添加 noStyle
作为纯粹的无样式绑定组件(类似 3.x 中的 getFieldDecorator
)。
- <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 只作为布局作用。
更复杂的封装复用方式可以参考下面的 自定义表单控件
演示。
import { Form, Input, Select, Tooltip, Button } from 'antd';
const { Option } = Select;
const Demo = () => {
const onFinish = values => {
console.log('Received values of form: ', values);
};
return (
<Form name="complex-form" onFinish={onFinish} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }}>
<Form.Item label="Username">
<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">
<a href="#API" style={{ marginLeft: 8 }}>
Need Help?
</a>
</Tooltip>
</Form.Item>
<Form.Item label="Address">
<Input.Group 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>
</Input.Group>
</Form.Item>
<Form.Item label="BirthDate" style={{ marginBottom: 0 }}>
<Form.Item
name="year"
rules={[{ required: true }]}
style={{ display: 'inline-block', width: 'calc(50% - 5px)', marginRight: 8 }}
>
<Input placeholder="Input birth year" />
</Form.Item>
<Form.Item
name="month"
rules={[{ required: true }]}
style={{ display: 'inline-block', width: 'calc(50% - 5px)' }}
>
<Input placeholder="Input birth month" />
</Form.Item>
</Form.Item>
<Form.Item label=" " colon={false}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
ReactDOM.render(<Demo />, mountNode);
自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件遵循以下的约定:
提供受控属性
value
或其它与valuePropName
的值同名的属性。提供
onChange
事件或trigger
的值同名的事件。
TypeScript
JavaScript
import React, { useState } from 'react';
import { Form, Input, Select, Button } from 'antd';
const { Option } = Select;
interface PriceValue {
number?: number;
currency?: 'rmb' | 'dollar';
}
interface PriceInputProps {
value?: PriceValue;
onChange?: (value: PriceValue) => void;
}
const PriceInput: React.FC<PriceInputProps> = ({ value = {}, onChange }) => {
const [number, setNumber] = useState(0);
const [currency, setCurrency] = useState('rmb');
const triggerChange = changedValue => {
if (onChange) {
onChange({ number, currency, ...value, ...changedValue });
}
};
const onNumberChange = e => {
const newNumber = parseInt(e.target.value || 0, 10);
if (Number.isNaN(number)) {
return;
}
if (!('number' in value)) {
setNumber(newNumber);
}
triggerChange({ number: newNumber });
};
const onCurrencyChange = newCurrency => {
if (!('currency' in value)) {
setCurrency(newCurrency);
}
triggerChange({ currency: newCurrency });
};
return (
<span>
<Input
type="text"
value={value.number || number}
onChange={onNumberChange}
style={{ width: 100, marginRight: 8 }}
/>
<Select value={value.currency || currency} style={{ width: 80 }} onChange={onCurrencyChange}>
<Option value="rmb">RMB</Option>
<Option value="dollar">Dollar</Option>
</Select>
</span>
);
};
const Demo = () => {
const onFinish = values => {
console.log('Received values from form: ', values);
};
const checkPrice = (rule, value) => {
if (value.number > 0) {
return Promise.resolve();
}
return Promise.reject('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>
);
};
ReactDOM.render(<Demo />, mountNode);
通过 onFieldsChange
和 fields
,可以把表单的数据存储到上层组件或者 Redux、dva 中,更多可参考 rc-field-form 示例。
注意: 将表单数据存储于外部容器并非好的实践,如无必要请避免使用。
TypeScript
JavaScript
import React, { useState } from 'react';
import { Form, Input } from 'antd';
interface FieldData {
name: string[];
value: any;
touched: boolean;
validating: boolean;
errors: string[];
}
interface CustomizedFormProps {
onChange: (fields: FieldData[]) => void;
fields: FieldData[];
}
const CustomizedForm: React.FC<CustomizedFormProps> = ({ onChange, fields }) => {
return (
<Form
name="global_state"
layout="inline"
fields={fields}
onFieldsChange={(changedFields, allFields) => {
onChange(allFields);
}}
>
<Form.Item
name="username"
label="Username"
rules={[{ required: true, message: 'Username is required!' }]}
>
<Input />
</Form.Item>
</Form>
);
};
const Demo = () => {
const [fields, setFields] = useState([{ name: ['username'], value: 'Ant Design' }]);
return (
<div>
<CustomizedForm
fields={fields}
onChange={newFields => {
setFields(newFields);
}}
/>
<pre className="language-bash">{JSON.stringify(fields, null, 2)}</pre>
</div>
);
};
ReactDOM.render(<Demo />, mountNode);
通过 Form.Provider
在表单间处理数据。本例子中,Modal 的确认按钮在 Form 之外,通过 form.submit
方法调用表单提交功能。反之,则推荐使用 <Button htmlType="submit" />
调用 web 原生提交逻辑。
TypeScript
JavaScript
import React, { useState, useEffect, useRef } from 'react';
import { Form, Input, InputNumber, Modal, Button, Avatar, Typography } from 'antd';
import { SmileOutlined, UserOutlined } from '@ant-design/icons';
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
const tailLayout = {
wrapperCol: { offset: 8, span: 16 },
};
interface ModalFormProps {
visible: boolean;
onCancel: () => void;
}
const ModalForm: React.FC<ModalFormProps> = ({ visible, onCancel }) => {
const [form] = Form.useForm();
const prevVisibleRef = useRef();
useEffect(() => {
prevVisibleRef.current = visible;
}, [visible]);
const prevVisible = prevVisibleRef.current;
useEffect(() => {
console.log(visible, prevVisible);
if (!visible && prevVisible) {
form.resetFields();
}
}, [visible]);
const onOk = () => {
form.submit();
};
return (
<Modal title="Basic Drawer" visible={visible} 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 Demo = () => {
const [visible, setVisible] = useState(false);
const showUserModal = () => {
setVisible(true);
};
const hideUserModal = () => {
setVisible(false);
};
const onFinish = values => {
console.log('Finish:', values);
};
return (
<div>
<Form.Provider
onFormFinish={(name, { values, forms }) => {
if (name === 'userForm') {
const { basicForm } = forms;
const users = basicForm.getFieldValue('users') || [];
basicForm.setFieldsValue({ users: [...users, values] });
setVisible(false);
}
}}
>
<Form {...layout} name="basicForm" onFinish={onFinish}>
<Form.Item name="group" label="Group Name" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item
label="User List"
shouldUpdate={(prevValues, curValues) => prevValues.users !== curValues.users}
>
{({ getFieldValue }) => {
const users = getFieldValue('users') || [];
return users.length ? (
<ul>
{users.map((user, index) => (
<li key={index} className="user">
<Avatar icon={<UserOutlined />} />
{user.name} - {user.age}
</li>
))}
</ul>
) : (
<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={{ marginLeft: 8 }} onClick={showUserModal}>
Add User
</Button>
</Form.Item>
</Form>
<ModalForm visible={visible} onCancel={hideUserModal} />
</Form.Provider>
</div>
);
};
ReactDOM.render(<Demo />, mountNode);
#components-form-demo-form-context .user {
margin-bottom: 8px;
}
#components-form-demo-form-context .user .ant-avatar {
margin-right: 8px;
}
内联登录栏,常用在顶部导航栏中。
TypeScript
JavaScript
import React, { useState, useEffect } from 'react';
import { Form, Input, Button } from 'antd';
import { UserOutlined, LockOutlined } from '@ant-design/icons';
const HorizontalLoginForm = () => {
const [form] = Form.useForm();
const [, forceUpdate] = useState();
// To disable submit button at the beginning.
useEffect(() => {
forceUpdate({});
}, []);
const onFinish = values => {
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 className="site-form-item-icon" />} placeholder="Username" />
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: 'Please input your password!' }]}
>
<Input
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder="Password"
/>
</Form.Item>
<Form.Item shouldUpdate={true}>
{() => (
<Button
type="primary"
htmlType="submit"
disabled={
!form.isFieldsTouched(true) ||
form.getFieldsError().filter(({ errors }) => errors.length).length
}
>
Log in
</Button>
)}
</Form.Item>
</Form>
);
};
ReactDOM.render(<HorizontalLoginForm />, mountNode);
普通的登录框,可以容纳更多的元素。
TypeScript
JavaScript
import { Form, Input, Button, Checkbox } from 'antd';
import { UserOutlined, LockOutlined } from '@ant-design/icons';
const NormalLoginForm = () => {
const onFinish = values => {
console.log('Received values of form: ', values);
};
return (
<Form
name="normal_login"
className="login-form"
initialValues={{ remember: true }}
onFinish={onFinish}
>
<Form.Item
name="username"
rules={[{ required: true, message: 'Please input your Username!' }]}
>
<Input prefix={<UserOutlined className="site-form-item-icon" />} placeholder="Username" />
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: 'Please input your Password!' }]}
>
<Input
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder="Password"
/>
</Form.Item>
<Form.Item>
<Form.Item name="remember" valuePropName="checked" noStyle>
<Checkbox>Remember me</Checkbox>
</Form.Item>
<a className="login-form-forgot" href="">
Forgot password
</a>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" className="login-form-button">
Log in
</Button>
Or <a href="">register now!</a>
</Form.Item>
</Form>
);
};
ReactDOM.render(<NormalLoginForm />, mountNode);
#components-form-demo-normal-login .login-form {
max-width: 300px;
}
#components-form-demo-normal-login .login-form-forgot {
float: right;
}
#components-form-demo-normal-login .login-form-button {
width: 100%;
}
用户填写必须的信息以注册新用户。
TypeScript
JavaScript
import React, { useState } from 'react';
import {
Form,
Input,
Tooltip,
Cascader,
Select,
Row,
Col,
Checkbox,
Button,
AutoComplete,
} from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
const { Option } = Select;
const AutoCompleteOption = AutoComplete.Option;
const residences = [
{
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 = {
labelCol: {
xs: { span: 24 },
sm: { span: 8 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 16,
offset: 8,
},
},
};
const RegistrationForm = () => {
const [form] = Form.useForm();
const onFinish = values => {
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 [autoCompleteResult, setAutoCompleteResult] = useState([]);
const onWebsiteChange = value => {
if (!value) {
setAutoCompleteResult([]);
} else {
setAutoCompleteResult(['.com', '.org', '.net'].map(domain => `${value}${domain}`));
}
};
const websiteOptions = autoCompleteResult.map(website => ({
label: website,
value: website,
}));
return (
<Form
{...formItemLayout}
form={form}
name="register"
onFinish={onFinish}
initialValues={{
residence: ['zhejiang', 'hangzhou', 'xihu'],
prefix: '86',
}}
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(rule, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve();
}
return Promise.reject('The two passwords that you entered do not match!');
},
}),
]}
>
<Input.Password />
</Form.Item>
<Form.Item
name="nickname"
label={
<span>
Nickname
<Tooltip title="What do you want others to call you?">
<QuestionCircleOutlined />
</Tooltip>
</span>
}
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="website"
label="Website"
rules={[{ required: true, message: 'Please input website!' }]}
>
<AutoComplete options={websiteOptions} onChange={onWebsiteChange} placeholder="website">
<Input />
</AutoComplete>
</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" {...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>
);
};
ReactDOM.render(<RegistrationForm />, mountNode);
三列栅格式的表单排列方式,常用于数据表格的高级搜索。
有部分定制的样式代码,由于输入标签长度不确定,需要根据具体情况自行调整。
TypeScript
JavaScript
import React, { useState } from 'react';
import { Form, Row, Col, Input, Button } from 'antd';
import { DownOutlined, UpOutlined } from '@ant-design/icons';
const AdvancedSearchForm = () => {
const [expand, setExpand] = useState(false);
const [form] = Form.useForm();
const getFields = () => {
const count = expand ? 10 : 6;
const children = [];
for (let i = 0; i < count; i++) {
children.push(
<Col span={8} key={i}>
<Form.Item
name={`field-${i}`}
label={`Field ${i}`}
rules={[
{
required: true,
message: 'Input something!',
},
]}
>
<Input placeholder="placeholder" />
</Form.Item>
</Col>,
);
}
return children;
};
const onFinish = values => {
console.log('Received values of form: ', values);
};
return (
<Form
form={form}
name="advanced_search"
className="ant-advanced-search-form"
onFinish={onFinish}
>
<Row gutter={24}>{getFields()}</Row>
<Row>
<Col span={24} style={{ textAlign: 'right' }}>
<Button type="primary" htmlType="submit">
Search
</Button>
<Button
style={{ marginLeft: 8 }}
onClick={() => {
form.resetFields();
}}
>
Clear
</Button>
<a
style={{ marginLeft: 8, fontSize: 12 }}
onClick={() => {
setExpand(!expand);
}}
>
{expand ? <UpOutlined /> : <DownOutlined />} Collapse
</a>
</Col>
</Row>
</Form>
);
};
ReactDOM.render(
<div>
<AdvancedSearchForm />
<div className="search-result-list">Search Result List</div>
</div>,
mountNode,
);
.ant-advanced-search-form {
padding: 24px;
background: #fbfbfb;
border: 1px solid #d9d9d9;
border-radius: 2px;
}
.ant-advanced-search-form .ant-form-item {
display: flex;
}
.ant-advanced-search-form .ant-form-item-control-wrapper {
flex: 1;
}
当用户访问一个展示了某个列表的页面,想新建一项但又不想跳转页面时,可以用 Modal 弹出一个表单,用户填写必要信息后创建新的项。
TypeScript
JavaScript
import React, { useState } from 'react';
import { Button, Modal, Form, Input, Radio } from 'antd';
interface Values {
title: string;
description: string;
modifier: string;
}
interface CollectionCreateFormProps {
visible: boolean;
onCreate: (values: Values) => void;
onCancel: () => void;
}
const CollectionCreateForm: React.FC<CollectionCreateFormProps> = ({
visible,
onCreate,
onCancel,
}) => {
const [form] = Form.useForm();
return (
<Modal
visible={visible}
title="Create a new collection"
okText="Create"
cancelText="Cancel"
onCancel={onCancel}
onOk={() => {
form
.validateFields()
.then(values => {
form.resetFields();
onCreate(values);
})
.catch(info => {
console.log('Validate Failed:', info);
});
}}
>
<Form
form={form}
layout="vertical"
name="form_in_modal"
initialValues={{ modifier: 'public' }}
>
<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>
</Form>
</Modal>
);
};
const CollectionsPage = () => {
const [visible, setVisible] = useState(false);
const onCreate = values => {
console.log('Received values of form: ', values);
setVisible(false);
};
return (
<div>
<Button
type="primary"
onClick={() => {
setVisible(true);
}}
>
New Collection
</Button>
<CollectionCreateForm
visible={visible}
onCreate={onCreate}
onCancel={() => {
setVisible(false);
}}
/>
</div>
);
};
ReactDOM.render(<CollectionsPage />, mountNode);
.collection-create-form_last-form-item {
margin-bottom: 0;
}
时间类组件的 value
类型为 moment
对象,所以在提交服务器前需要预处理。
TypeScript
JavaScript
import { Form, DatePicker, TimePicker, Button } from 'antd';
const { MonthPicker, RangePicker } = DatePicker;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 8 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
const config = {
rules: [{ type: 'object', required: true, message: 'Please select time!' }],
};
const rangeConfig = {
rules: [{ type: 'array', required: true, message: 'Please select time!' }],
};
const TimeRelatedForm = () => {
const onFinish = fieldsValue => {
// 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);
};
return (
<Form name="time_related_controls" {...formItemLayout} onFinish={onFinish}>
<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}>
<MonthPicker />
</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
wrapperCol={{
xs: { span: 24, offset: 0 },
sm: { span: 16, offset: 8 },
}}
>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
ReactDOM.render(<TimeRelatedForm />, mountNode);
Form
具有自动收集数据并校验的功能,但如果您不需要这个功能,或者默认的行为无法满足业务需求,可以选择自行处理数据。
TypeScript
JavaScript
import React, { useState } from 'react';
import { Form, InputNumber } from 'antd';
function validatePrimeNumber(number) {
if (number === 11) {
return {
validateStatus: 'success',
errorMsg: null,
};
}
return {
validateStatus: 'error',
errorMsg: 'The prime between 8 and 12 is 11!',
};
}
const formItemLayout = {
labelCol: { span: 7 },
wrapperCol: { span: 12 },
};
const RawForm = () => {
const [number, setNumber] = useState({
value: 11,
});
const tips =
'A prime is a natural number greater than 1 that has no positive divisors other than 1 and itself.';
const onNumberChange = value => {
setNumber({
...validatePrimeNumber(value),
value,
});
};
return (
<Form>
<Form.Item
{...formItemLayout}
label="Prime between 8 & 12"
validateStatus={number.validateStatus}
help={number.errorMsg || tips}
>
<InputNumber min={8} max={12} value={number.value} onChange={onNumberChange} />
</Form.Item>
</Form>
);
};
ReactDOM.render(<RawForm />, mountNode);
我们提供了 validateStatus
help
hasFeedback
等属性,你可以不通过 Form 自己定义校验的时机和内容。
validateStatus
: 校验状态,可选 'success', 'warning', 'error', 'validating'。hasFeedback
:用于给输入框添加反馈图标。help
:设置校验文案。
TypeScript
JavaScript
import { SmileOutlined } from '@ant-design/icons';
import { Form, Input, DatePicker, TimePicker, Select, Cascader, InputNumber } from 'antd';
const { Option } = Select;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
},
};
ReactDOM.render(
<Form {...formItemLayout}>
<Form.Item
label="Fail"
validateStatus="error"
help="Should be combination of numbers & alphabets"
>
<Input placeholder="unavailable choice" id="error" />
</Form.Item>
<Form.Item label="Warning" validateStatus="warning">
<Input placeholder="Warning" id="warning" 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">
<Select>
<Option value="1">Option 1</Option>
<Option value="2">Option 2</Option>
<Option value="3">Option 3</Option>
</Select>
</Form.Item>
<Form.Item
label="Validating"
hasFeedback
validateStatus="validating"
help="The information is being validated..."
>
<Cascader options={[]} />
</Form.Item>
<Form.Item label="inline" style={{ marginBottom: 0 }}>
<Form.Item
validateStatus="error"
help="Please select the correct date"
style={{ display: 'inline-block', width: 'calc(50% - 12px)' }}
>
<DatePicker />
</Form.Item>
<span style={{ display: 'inline-block', width: '24px', textAlign: 'center' }}>-</span>
<Form.Item style={{ display: 'inline-block', width: 'calc(50% - 12px)' }}>
<DatePicker />
</Form.Item>
</Form.Item>
<Form.Item label="Success" hasFeedback validateStatus="success">
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.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>,
mountNode,
);
根据不同情况执行不同的校验规则。
TypeScript
JavaScript
import React, { useState, useEffect } from 'react';
import { Form, Input, Button, Checkbox } from 'antd';
const formItemLayout = {
labelCol: { span: 4 },
wrapperCol: { span: 8 },
};
const formTailLayout = {
labelCol: { span: 4 },
wrapperCol: { span: 8, offset: 4 },
};
const DynamicRule = () => {
const [form] = Form.useForm();
const [checkNick, setCheckNick] = useState(false);
useEffect(() => {
form.validateFields(['nickname']);
}, [checkNick]);
const onCheckboxChange = e => {
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">
<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>
);
};
ReactDOM.render(<DynamicRule />, mountNode);
以上演示没有出现的表单控件对应的校验演示。
TypeScript
JavaScript
import {
Form,
Select,
InputNumber,
Switch,
Radio,
Slider,
Button,
Upload,
Rate,
Checkbox,
Row,
Col,
} from 'antd';
import { UploadOutlined, InboxOutlined } from '@ant-design/icons';
const { Option } = Select;
const formItemLayout = {
labelCol: { span: 6 },
wrapperCol: { span: 14 },
};
const normFile = e => {
console.log('Upload event:', e);
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
};
const Demo = () => {
const onFinish = values => {
console.log('Received values of form: ', values);
};
return (
<Form
name="validate_other"
{...formItemLayout}
onFinish={onFinish}
initialValues={{
['input-number']: 3,
['checkbox-group']: ['A', 'B'],
rate: 3.5,
}}
>
<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"> 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">
<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 style={{ width: '100%' }}>
<Row>
<Col span={8}>
<Checkbox value="A">A</Checkbox>
</Col>
<Col span={8}>
<Checkbox disabled value="B">
B
</Checkbox>
</Col>
<Col span={8}>
<Checkbox value="C">C</Checkbox>
</Col>
<Col span={8}>
<Checkbox value="D">D</Checkbox>
</Col>
<Col span={8}>
<Checkbox value="E">E</Checkbox>
</Col>
</Row>
</Checkbox.Group>
</Form.Item>
<Form.Item 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>
<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 wrapperCol={{ span: 12, offset: 6 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
ReactDOM.render(<Demo />, mountNode);
API
Form
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
component | 设置 Form 渲染元素,为 false 则不创建 DOM 节点 | ComponentType | false | form |
colon | 配置 Form.Item 的 colon 的默认值。表示是否显示 label 后面的冒号 (只有在属性 layout 为 horizontal 时有效) | boolean | true |
fields | 通过状态管理(如 redux)控制表单字段,如非强需求不推荐使用。查看示例 | FieldData[] | - |
form | 经 Form.useForm() 创建的 form 控制实例,不提供时会自动创建 | FormInstance | - |
hideRequiredMark | 隐藏所有表单项的必选标记 | boolean | false |
initialValues | 表单默认值,只有初始化以及重置时生效 | object | - |
labelAlign | label 标签的文本对齐方式 | left | right | right |
labelCol | label 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12} 或 sm: {span: 3, offset: 12} | object | - |
layout | 表单布局 | horizontal | vertical | inline | horizontal |
name | 表单名称,会作为表单字段 id 前缀使用 | string | - |
scrollToFirstError | 提交失败自动滚动到第一个错误字段 | false | - |
size | 设置字段组件的尺寸(仅限 antd 组件) | small | middle | large | - |
validateMessages | 验证提示模板,说明见下 | ValidateMessages | - |
wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | object | - |
onFinish | 提交表单且数据验证成功后回调事件 | Function(values) | - |
onFinishFailed | 提交表单且数据验证失败后回调事件 | Function({ values, errorFields, outOfDate }) | - |
onFieldsChange | 字段更新时触发回调事件 | Function(changedFields, allFields) | - |
onValuesChange | 字段值更新时触发回调事件 | Function(changedValues, allValues) | - |
validateMessages
Form 为验证提供了默认的错误提示信息,你可以通过配置 validateMessages
属性,修改对应的提示模板。一种常见的使用方式,是配置国际化提示信息:
const validateMessages = {
required: "'${name}' 是必选字段",
// ...
};
<Form validateMessages={validateMessages} />;
此外,ConfigProvider 也提供了全局化配置方案,允许统一配置错误提示模板:
const validateMessages = {
required: "'${name}' 是必选字段",
// ...
};
<ConfigProvider form={{ validateMessages }}>
<Form />
</ConfigProvider>;
Form.Item
表单字段组件,用于数据双向绑定、校验、布局等。
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
colon | 配合 label 属性使用,表示是否显示 label 后面的冒号 | boolean | true |
dependencies | 设置依赖字段,说明见下 | NamePath[] | - |
extra | 额外的提示信息,和 help 类似,当需要错误信息和提示文案同时出现时,可以使用这个。 | string|ReactNode | - |
getValueFromEvent | 设置如何将 event 的值转换成字段值 | (..args: any[]) => any | - |
hasFeedback | 配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用 | boolean | false |
help | 提示信息,如不设置,则会根据校验规则自动生成 | string|ReactNode | - |
htmlFor | 设置子元素 label htmlFor 属性 | string | - |
noStyle | 为 true 时不带样式,作为纯字段控件使用 | boolean | false |
label | label 标签的文本 | string|ReactNode | - |
labelAlign | 标签文本对齐方式 | left | right | right |
labelCol | label 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12} 或 sm: {span: 3, offset: 12} 。你可以通过 Form 的 labelCol 进行统一设置。当和 Form 同时设置时,以 Item 为准 | object | - |
name | 字段名,支持数组 | NamePath | - |
normalize | 转换字段值给控件 | (value, prevValue, prevValues) => any | - |
required | 是否必填,如不设置,则会根据校验规则自动生成 | boolean | false |
rules | 校验规则,设置字段的校验逻辑。点击此处查看示例 | Rule[] | - |
shouldUpdate | 自定义字段更新逻辑,说明见下 | boolean | (prevValue, curValue) => boolean | false |
trigger | 设置收集字段值变更的时机 | string | onChange |
validateFirst | 当某一规则校验不通过时,是否停止剩下的规则的校验 | boolean | false |
validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' | string | - |
validateTrigger | 设置字段校验的时机 | string | string[] | onChange |
valuePropName | 子节点的值的属性,如 Switch 的是 'checked' | string | 'value' |
wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol 。你可以通过 Form 的 wrapperCol 进行统一设置。当和 Form 同时设置时,以 Item 为准。 | object | - |
dependencies
当字段间存在依赖关系时使用。如果一个字段设置了 dependencies
属性。那么它所依赖的字段更新时,该字段将自动触发更新与校验。一种常见的场景,就是注册用户表单的“密码”与“确认密码”字段。“确认密码”校验依赖于“密码”字段,设置 dependencies
后,“密码”字段更新会重新触发“校验密码”的校验逻辑。你可以参考具体例子。
shouldUpdate
Form 通过增量更新方式,只更新被修改的字段相关组件以达到性能优化目的。大部分场景下,你只需要编写代码或者与 dependencies
属性配合校验即可。而在某些特定场景,例如修改某个字段值后出现新的字段选项、或者纯粹希望表单任意变化都对某一个区域进行渲染。你可以通过 shouldUpdate
修改 Form.Item 的更新逻辑。
当 shouldUpdate
为 true
时,Form 的任意变化都会使该 Form.Item 重新渲染。这对于自定义渲染一些区域十分有帮助:
<Form.Item shouldUpdate>
{() => {
return <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>;
}}
</Form.Item>
你可以参考示例查看具体使用场景。
当 shouldUpdate
为方法时,表单的每次数值更新都会调用该方法,提供原先的值与当前的值以供你比较是否需要更新。这对于是否根据值来渲染额外字段十分有帮助:
<Form.Item shouldUpdate={(prevValues, curValues) => prevValues.additional !== curValues.additional}>
{() => {
return (
<Form.Item name="other">
<Input />
</Form.Item>
);
}}
</Form.Item>
你可以参考示例查看具体使用场景。
Form.List
为字段提供数组化管理。
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
name | 字段名,支持数组 | NamePath | - |
children | 渲染函数 | (fields: Field[], operation: { add, remove, move }) => React.ReactNode | - |
<Form.List>
{fields => (
<div>
{fields.map(field => (
<Form.Item {...field}>
<Input />
</Form.Item>
))}
</div>
)}
</Form.List>
Form.Provider
提供表单间联动功能,其下设置 name
的 Form 更新时,会自动触发对应事件。查看示例。
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
onFormChange | 子表单字段更新时触发 | Function(formName: string, info: { changedFields, forms }) | - |
onFormFinish | 子表单提交时触发 | Function(formName: string, info: { values, forms }) | - |
<Form.Provider
onFormFinish={name => {
if (name === 'form1') {
// Do something...
}
}}
>
<Form name="form1">...</Form>
<Form name="form2">...</Form>
</Form.Provider>
FormInstance
名称 | 说明 | 类型 |
---|---|---|
getFieldValue | 获取对应字段名的值 | (name: NamePath) => any |
getFieldsValue | 获取一组字段名对应的值,会按照对应结构返回 | (nameList?: NamePath[], filterFunc?: (meta: { touched: boolean, validating: boolean }) => boolean) => any |
getFieldError | 获取对应字段名的错误信息 | (name: NamePath) => string[] |
getFieldsError | 获取一组字段名对应的错误信息,返回为数组形式 | (nameList?: NamePath[]) => FieldError[] |
isFieldTouched | 检查对应字段是否被用户操作过 | (name: NamePath) => boolean |
isFieldsTouched | 检查一组字段是否被用户操作过,allTouched 为 true 时检查是否所有字段都被操作过 | (nameList?: NamePath[], allTouched?: boolean) => boolean |
isFieldValidating | 检查一组字段是否正在校验 | (name: NamePath) => boolean |
resetFields | 重置一组字段到 initialValues | (fields?: NamePath[]) => void |
scrollToField | 滚动到对应字段位置 | (name: NamePath, options: [ScrollOptions]) => void |
setFields | 设置一组字段状态 | (fields: FieldData[]) => void |
setFieldsValue | 设置表单的值 | (values) => void |
submit | 提交表单,与点击 submit 按钮效果相同 | () => void |
validateFields | 触发表单验证 | (nameList?: NamePath[]) => Promise |
validateFields 返回示例
validateFields()
.then(values => {
/*
values:
{
username: 'username',
password: 'password',
}
*/
})
.catch(errorInfo => {
/*
errorInfo:
{
values: {
username: 'username',
password: 'password',
},
errorFields: [
{ password: ['username'], errors: ['Please input your Password!'] },
],
outOfDate: false,
}
*/
});
Interface
NamePath
string | number | (string | number)[]
FieldData
名称 | 说明 | 类型 |
---|---|---|
touched | 是否被用户操作过 | boolean |
validating | 是否正在校验 | boolean |
errors | 错误信息 | string[] |
name | 字段名称 | NamePath[] |
value | 字段对应值 | any |
Rule
Rule 支持接收 object 进行配置,也支持 function 来动态获取 from 的数据:
type Rule = RuleConfig | ((form: FormInstance) => RuleConfig);
名称 | 说明 | 类型 |
---|---|---|
enum | 是否匹配枚举中的值 | any[] |
len | string 类型时为字符串长度;number 类型时为确定数字; array 类型时为数组长度 | number |
max | string 类型为字符串最大长度;number 类型时为最大值;array 类型时为数组最大长度 | number |
message | 错误信息,不设置时会通过模板自动生成 | string |
min | string 类型为字符串最小长度;number 类型时为最小值;array 类型时为数组最小长度 | number |
pattern | 正则表达式匹配 | RegExp |
required | 是否为必选字段 | boolean |
transform | 将字段值转换成目标值后进行校验 | (value) => any |
type | 类型,常见有 string |number |boolean |url | email 。更多请参考此处 | string |
validator | 自定义校验,接收 Promise 作为返回值。示例参考 | (rule, value) => Promise |
whitespace | 如果字段仅包含空格则校验不通过 | boolean |
validateTrigger | 设置触发验证时机,必须是 Form.Item 的 validateTrigger 的子集 | string | string[] |
从 v3 升级到 v4
如果你是 antd v3 的用户,你可以参考迁移示例。
FAQ
自定义 validator 没有效果
这是由于你的 validator
有错误导致 callback
没有执行到。你可以选择通过 async
返回一个 promise 或者使用 try…catch
进行错误捕获:
validator: async (rule, value) => {
throw new Error('Something wrong!');
}
// or
validator(rule, value, callback) => {
try {
throw new Error('Something wrong!');
} catch (err) {
callback(err);
}
}
为何在 Modal 中调用 form 控制台会报错?
Warning: Instance created by
useForm
is not connect to any Form element. Forget to passform
prop?
这是因为你在调用 form 方法时,Modal 还未初始化导致 form 没有关联任何 Form 组件。你可以通过给 Modal 设置 forceRender
将其预渲染。示例点击此处。