폼
폼 (Form)
사용자로부터 정보를 입력받는 데 쓰는 컴포넌트예요. 데이터 수집, 검증, 제출까지 한 번에 처리해요. 로그인, 회원가입, 설정 페이지처럼 사용자 입력이 필요한 곳이면 어디든 쓸 수 있어요.
출처: 문서
본문
언제 사용하나요 (When To Use)
- 사용자로부터 정보를 입력받아야 할 때 사용해요.
- 데이터 수집과 검증, 제출 흐름이 필요할 때 사용해요.
예시 (Examples)
기본 (Basic)
기본적인 폼이에요. 제출 버튼을 누르면 입력받은 값이 콘솔에 출력돼요.
import React from 'react';
import { Button, Checkbox, Form, Input, InputNumber } from 'antd';
const App: React.FC = () => {
const [form] = Form.useForm();
const onFinish = (values: any) => {
console.log('Success:', values);
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
return (
<Form
name="basic"
form={form}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
style={{ maxWidth: 600 }}
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 name="remember" valuePropName="checked" wrapperCol={{ offset: 8, span: 16 }}>
<Checkbox>Remember me</Checkbox>
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
export default App;
기본 사용법 (Basic usage)
Form은 제어되지 않는 폼(Form)과 제어되는 폼(FormControl) 두 가지 사용법을 제공해요. 제어되는 폼이 더 많은 기능을 제공하지만, 기본 데모에서는 오래된 제어되지 않는 방식을 먼저 보여 줘요.
import React from 'react';
import { Button, Form, Input } from 'antd';
const App: React.FC = () => (
<Form name="basic" labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }}>
<Form.Item label="Username" name="username" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item label="Password" name="password" rules={[{ required: true }]}>
<Input.Password />
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
export default App;
폼 컨트롤 (Form Control)
제어되는 폼(FormControl)으로는 동적 필드 추가, 실시간 검증, 폼 값 감시 등 더 풍부한 기능을 쓸 수 있어요.
import React from 'react';
import { Button, Form, Input } from 'antd';
const App: React.FC = () => {
const [form] = Form.useForm();
const onFinish = (values: any) => {
console.log('Success:', values);
};
const onFill = () => {
form.setFieldsValue({
user: 'Ant Design',
note: 'Hello world!',
});
};
return (
<Form
form={form}
onFinish={onFinish}
variant="filled"
style={{ maxWidth: 600 }}
>
<Form.Item label="Note" name="note" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item label="User" name="user" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item>
<Button htmlType="submit" type="primary">
Submit
</Button>
<Button htmlType="button" onClick={onFill}>
Fill form
</Button>
</Form.Item>
</Form>
);
};
export default App;
레이아웃 (Layout)
폼에는 horizontal, vertical, inline 세 가지 레이아웃이 있어요. layout prop으로 설정하고, 기본값은 horizontal이에요.
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;
검증 규칙 (Validate Rules)
rules로 폼 필드의 검증을 설정할 수 있어요. 필수 여부, 최소/최대 길이, 이메일 형식, 커스텀 검증 함수 등을 지원해요.
import React from 'react';
import { Button, Form, Input, Select } from 'antd';
const { Option } = Select;
const App: React.FC = () => {
const [form] = Form.useForm();
const onFinish = (values: any) => {
console.log('Received values of 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
form={form}
name="register"
onFinish={onFinish}
initialValues={{ residence: ['zhejiang', 'hangzhou', 'xihu'] }}
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 label="Captcha" extra="We must make sure that your are a human.">
<div>
<Input />
<Button>Get captcha</Button>
</div>
</Form.Item>
<Form.Item
name="price"
label="Price"
rules={[{ required: true, message: 'Please input price!' }, { validator: checkPrice }]}
>
<Input type="number" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Register
</Button>
</Form.Item>
</Form>
);
};
export default App;
동적 폼 항목 (Dynamic Form Item)
폼 항목을 동적으로 추가·제거할 수 있어요. Form.List를 사용해 항목 배열을 관리해요.
import React from 'react';
import { Button, Form, Input, Space } from 'antd';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
const App: React.FC = () => {
const onFinish = (values: any) => {
console.log('Received values of form:', values);
};
return (
<Form name="dynamic_form_nest_item" onFinish={onFinish} style={{ maxWidth: 600 }}>
<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;
커스터마이즈된 폼 컨트롤 (Customized Form Controls)
value와 onChange만 처리하면 어떤 컴포넌트든 폼 컨트롤로 쓸 수 있어요.
import React, { useState } from 'react';
import { Button, Form, Input, Select } from 'antd';
const { Option } = Select;
type Currency = 'RMB' | 'USD';
interface PriceValue {
number?: number;
currency?: Currency;
}
interface PriceInputProps {
value?: PriceValue;
onChange?: (value: PriceValue) => void;
}
const PriceInput: React.FC<PriceInputProps> = ({ value = {}, onChange }) => {
const [number, setNumber] = useState(0);
const [currency, setCurrency] = useState<Currency>('RMB');
const triggerChange = (changedValue: PriceValue) => {
onChange?.({ number, currency, ...value, ...changedValue });
};
const onNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newNumber = parseInt(e.target.value || '0', 10);
if (Number.isNaN(number)) {
return;
}
setNumber(newNumber);
triggerChange({ number: newNumber });
};
const onCurrencyChange = (newCurrency: Currency) => {
setCurrency(newCurrency);
triggerChange({ currency: newCurrency });
};
return (
<span>
<Input type="text" value={value.number || number} onChange={onNumberChange} placeholder="Input number" />
<Select value={value.currency || currency} style={{ width: 80 }} onChange={onCurrencyChange}>
<Option value="RMB">RMB</Option>
<Option value="USD">USD</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 >= 1) {
return Promise.resolve();
}
return Promise.reject(new Error('Price must be greater than 0!'));
};
return (
<Form name="customized_form_controls" onFinish={onFinish} style={{ maxWidth: 600 }}>
<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;
API
공통 props 참조: Common props
Form
Form 컴포넌트의 기본 props예요.
| 속성 | 설명 | 타입 | 기본값 |
|---|---|---|---|
| component | 커스텀 컴포넌트로 <form>을 대체 |
ComponentType | false | form |
| colon | label 뒤에 콜론을 표시할지 여부 | boolean | true |
| disabled | 폼 안의 모든 하위 컴포넌트를 비활성화 | boolean | false |
| form | Form.useForm()으로 만든 폼 인스턴스 |
FormInstance | - |
| labelAlign | label의 정렬 방식 | left | right |
right |
| labelCol | label의 레이아웃 | object | - |
| name | 폼 이름 | string | - |
| preserve | 필드가 제거되었을 때 값을 보존할지 여부 | boolean | true |
| requiredMark | 필수 필드 표시 방식 | boolean | optional | (label, info) => ReactNode |
true |
| scrollToFirstError | 제출 실패 시 첫 번째 에러로 스크롤 | boolean | options | false |
| size | 폼 안 컴포넌트의 크기 | large | middle | small |
- |
| validateMessages | 검증 메시지 템플릿 | ValidateMessages | - |
| validateTrigger | 검증을 트리거할 이벤트 | string | string[] | onChange |
| variant | 컴포넌트의 변형 | outlined | borderless | filled | underlined |
- |
| wrapperCol | 입력 컨트롤의 레이아웃 | object | - |
| onFinish | 제출 성공 시 호출 | (values) => void | - |
| onFinishFailed | 제출 실패 시 호출 | ({ values, errorFields, outOfDate }) => void | - |
| onValuesChange | 값이 변경될 때 호출 | (changedValues, allValues) => void | - |
Form.Item
| 속성 | 설명 | 타입 | 기본값 |
|---|---|---|---|
| dependencies | 다른 필드의 값을 의존 | NamePath[] | - |
| extra | 추가 도움말을 표시 | ReactNode | - |
| getValueFromEvent | 이벤트에서 값을 추출하는 함수 | (..args) => any | - |
| getValueProps | 입력 컴포넌트에 넘기는 props를 변환 | (value) => Record<string, any> | - |
| hasFeedback | 검증 피드백 아이콘을 표시 | boolean | false |
| help | 도움말 텍스트 | ReactNode | - |
| hidden | 필드를 숨길지 여부 | boolean | false |
| htmlFor | label의 htmlFor 속성 | string | - |
| initialValue | 초기값 | any | - |
| label | 필드의 라벨 | ReactNode | - |
| name | 필드 이름 | NamePath | - |
| normalize | 값 변경 시 정규화 함수 | (value, prevValue, allValues) => any | - |
| noStyle | 스타일 없는 필드 | boolean | false |
| preserve | 값 보존 여부 | boolean | true |
| required | 필수 여부(라벨에 별표 표시) | boolean | false |
| rules | 검증 규칙 | Rule[] | - |
| shouldUpdate | 다른 필드 변경에 반응해 다시 렌더링 | boolean | (prevValues, curValues) => boolean | false |
| trigger | 값을 트리거할 이벤트 | string | onChange |
| validateDebounce | 검증 디바운스 | number | - |
| validateFirst | 첫 번째 실패에서 멈출지 여부 | boolean | parallel |
false |
| validateStatus | 검증 상태 강제 지정 | success | warning | error | validating |
- |
| validateTrigger | 검증 트리거 이벤트 | string | string[] | onChange |
| valuePropName | 값이 담긴 prop 이름 | string | value |
| wrapperCol | 입력 컨트롤 레이아웃 | object | - |
Form.List
| 속성 | 설명 | 타입 | 기본값 |
|---|---|---|---|
| children | 리스트 렌더링 함수 | (fields, { add, remove, move, insert, replace }) => ReactNode | - |
| initialValue | 초기값 | any[] | - |
| name | 폼 리스트 이름 | NamePath | - |
| rules | 검증 규칙 | Rule[] | - |
FormInstance
Form.useForm()으로 얻는 인스턴스의 메서드예요.
| 메서드 | 설명 |
|---|---|
| getFieldValue(name) | 필드 값을 가져옴 |
| getFieldsValue() | 모든 필드 값을 가져옴 |
| getFieldsError() | 모든 필드의 에러를 가져옴 |
| isFieldsTouched() | 필드가 터치되었는지 확인 |
| isFieldTouched(name) | 특정 필드가 터치되었는지 확인 |
| isFieldValidating(name) | 특정 필드가 검증 중인지 확인 |
| resetFields(fields?) | 필드 값을 초기값으로 리셋 |
| scrollToField(name, options?) | 특정 필드로 스크롤 |
| setFields(fields) | 필드 상태를 설정 |
| setFieldsValue(values) | 필드 값을 설정 |
| submit() | 폼을 제출 |
| validateFields(nameList?) | 필드를 검증. Promise 반환 |
Rule
검증 규칙 객체의 속성이에요.
| 속성 | 설명 | 타입 |
|---|---|---|
| enum | 열거형 값과 일치하는지 검증 | any[] |
| len | 정확한 길이 검증 | number |
| max | 최대 길이/값 검증 | number |
| message | 에러 메시지 | ReactNode |
| min | 최소 길이/값 검증 | number |
| pattern | 정규식 패턴 검증 | RegExp |
| required | 필수 여부 | boolean |
| transform | 검증 전 값 변환 | (value) => any |
| type | 값 타입 검증 | string |
| validator | 커스텀 검증 함수 | (rule, value) => Promise | void |
| whitespace | 공백만 있는지 검증 | boolean |
Semantic DOM
https://ant.design/components/form/semantic.md
Design Token
Component Token (Form)
| 토큰 이름 | 설명 | 타입 | 기본값 |
|---|---|---|---|
| itemMarginBottom | 필드 항목 사이의 아래 여백 | number |
FAQ
context, redux, ConfigProvider locale/prefixCls에 접근할 수 없는 이유는 뭔가요? {#faq-context-redux}
정적 메서드(message, notification, Modal.confirm 등)를 사용할 때는 antd가 ReactDOM.render로 별도의 React 인스턴스를 만들어 컨텍스트를 공유하지 못할 수 있어요. 폼 안에서 이런 메서드를 쓸 때는 Form.useFormInstance()나 App 컴포넌트의 인스턴스를 사용하면 컨텍스트 문제를 피할 수 있어요.
검증 규칙의 validator를 비동기로 사용할 수 있나요?
네, validator는 Promise를 반환할 수 있어요. resolve하면 통과, reject하면 실패로 처리돼요.