Form 폼
데이터를 수집하고 검증한 뒤 제출까지 처리할 수 있는 폼 컴포넌트로, 입력창·라디오버튼·체크박스·셀렉트박스 등 다양한 요소를 담을 수 있어요.
출처: 문서
본문
언제 사용하나요?
데이터 수집, 데이터 검증, 데이터 제출 기능이 필요할 때 사용해요.
기본 사용법
:::demo 기본으로 가로(수평) 레이아웃을 제공하고, data 파라미터로 폼 데이터를 설정해요.
<template>
<d-form :data="formModel">
<d-form-item field="name" label="Name" help-tips="This is the plan name." extra-info="Enter a short name that meets reading habits.">
<d-input v-model="formModel.name" />
</d-form-item>
<d-form-item field="description" label="Description">
<d-textarea v-model="formModel.description" />
</d-form-item>
<d-form-item field="select" label="Select">
<d-select v-model="formModel.select" :options="selectOptions" />
</d-form-item>
<d-form-item field="radio" label="Radio">
<d-radio-group direction="row" v-model="formModel.radio">
<d-radio value="0">Manual execution</d-radio>
<d-radio value="1">Daily execution</d-radio>
<d-radio value="2">Weekly execution</d-radio>
</d-radio-group>
</d-form-item>
<d-form-item field="switch" label="Switch">
<d-switch v-model="formModel.switch"></d-switch>
</d-form-item>
<d-form-item field="executionDay">
<template #label><span>Execution day</span></template>
<d-checkbox-group v-model="formModel.executionDay" label="Execution day" direction="row">
<d-checkbox label="Mon" value="Mon" />
<d-checkbox label="Tue" value="Tue" />
<d-checkbox label="Wed" value="Wed" />
<d-checkbox label="Thur" value="Thur" />
<d-checkbox label="Fri" value="Fri" />
<d-checkbox label="Sat" value="Sat" />
<d-checkbox label="Sun" value="Sun" />
</d-checkbox-group>
</d-form-item>
<d-form-operation class="form-demo-form-operation">
<d-button variant="solid">提交</d-button>
<d-button>取消</d-button>
</d-form-operation>
</d-form>
</template>
<script>
import { defineComponent, reactive, ref, nextTick } from 'vue';
export default defineComponent({
setup() {
let formModel = reactive({
name: '',
description: '',
select: 'Options2',
radio: '0',
switch: true,
executionDay: [],
});
const selectOptions = reactive(['Options1', 'Options2', 'Options3']);
return {
formModel,
selectOptions,
};
},
});
</script>
<style>
.form-demo-form-operation > * {
margin-right: 8px;
}
</style>
:::
폼 스타일
:::demo 가로 배치 모드에서는 label-size로 label의 너비를 설정할 수 있어요. sm, md, lg 세 가지 크기를 제공하며 각각 80px, 100px, 150px에 대응하고 기본값은 md예요. label-align으로 label의 정렬 방식을 설정할 수 있는데, start, center, end 중에서 선택할 수 있고 기본값은 start예요.
<template>
<div class="form-btn-groups">
<div class="form-btn">
大小:
<d-radio-group direction="row" v-model="size">
<d-radio v-for="item in sizeList" :key="item.label" :value="item.value">
{{ item.label }}
</d-radio>
</d-radio-group>
</div>
<div class="form-btn">
对齐方式:
<d-radio-group direction="row" v-model="align">
<d-radio v-for="item in alignList" :key="item.label" :value="item.value">
{{ item.label }}
</d-radio>
</d-radio-group>
</div>
</div>
<d-form :data="formModel" :label-size="size" :label-align="align">
<d-form-item field="name" label="Name">
<d-input v-model="formModel.name" />
</d-form-item>
<d-form-item field="description" label="Description">
<d-textarea v-model="formModel.description" />
</d-form-item>
<d-form-item field="executionDay" label="Execution day">
<d-checkbox-group v-model="formModel.executionDay" label="Execution day" direction="row">
<d-checkbox label="Mon" value="Mon" />
<d-checkbox label="Tue" value="Tue" />
<d-checkbox label="Wed" value="Wed" />
<d-checkbox label="Thur" value="Thur" />
<d-checkbox label="Fri" value="Fri" />
<d-checkbox label="Sat" value="Sat" />
<d-checkbox label="Sun" value="Sun" />
</d-checkbox-group>
</d-form-item>
<d-form-operation class="form-demo-form-operation">
<d-button variant="solid">提交</d-button>
<d-button>取消</d-button>
</d-form-operation>
</d-form>
</template>
<script>
import { defineComponent, reactive, ref } from 'vue';
export default defineComponent({
setup() {
const formModel = reactive({
name: '',
description: '',
executionDay: [],
});
const size = ref('md');
const align = ref('start');
const sizeList = [
{
label: 'Small',
value: 'sm',
},
{
label: 'Middle',
value: 'md',
},
{
label: 'Large',
value: 'lg',
},
];
const alignList = [
{
label: 'Start',
value: 'start',
},
{
label: 'Center',
value: 'center',
},
{
label: 'End',
value: 'end',
},
];
return {
formModel,
size,
sizeList,
align,
alignList,
};
},
});
</script>
<style>
.form-btn-groups {
display: flex;
flex-wrap: wrap;
margin-bottom: 16px;
}
.form-btn {
display: flex;
justify-content: flex-start;
align-items: center;
margin-right: 1rem;
}
</style>
:::
세로 배치
:::demo layout 파라미터를 vertical로 설정하면 세로 레이아웃이 활성화되고, 이때 label이 입력 컨트롤 위쪽에 표시돼요.
<template>
<d-form layout="vertical" :data="formModel">
<d-form-item field="name" label="Name">
<d-input v-model="formModel.name" />
</d-form-item>
<d-form-item field="description" label="Description">
<d-textarea v-model="formModel.description" />
</d-form-item>
<d-form-item field="select" label="Select">
<d-select v-model="formModel.select" :options="selectOptions" />
</d-form-item>
<d-form-item field="radio" label="Radio">
<d-radio-group direction="row" v-model="formModel.radio">
<d-radio value="0">Manual execution</d-radio>
<d-radio value="1">Daily execution</d-radio>
<d-radio value="2">Weekly execution</d-radio>
</d-radio-group>
</d-form-item>
<d-form-item field="switch" label="Switch">
<d-switch v-model="formModel.switch"></d-switch>
</d-form-item>
<d-form-item field="executionDay" label="Execution day">
<d-checkbox-group v-model="formModel.executionDay" label="Execution day" direction="row">
<d-checkbox label="Mon" value="Mon" />
<d-checkbox label="Tue" value="Tue" />
<d-checkbox label="Wed" value="Wed" />
<d-checkbox label="Thur" value="Thur" />
<d-checkbox label="Fri" value="Fri" />
<d-checkbox label="Sat" value="Sat" />
<d-checkbox label="Sun" value="Sun" />
</d-checkbox-group>
</d-form-item>
<d-form-operation class="form-demo-form-operation">
<d-button variant="solid">提交</d-button>
<d-button>取消</d-button>
</d-form-operation>
</d-form>
</template>
<script>
import { defineComponent, reactive } from 'vue';
export default defineComponent({
setup() {
let formModel = reactive({
name: '',
description: '',
select: 'Options2',
radio: '0',
switch: true,
executionDay: [],
});
const selectOptions = reactive(['Options1', 'Options2', 'Options3']);
return {
formModel,
selectOptions,
};
},
});
</script>
:::
다열 폼
:::demo Grid 그리드 레이아웃과 함께 사용하면 다열 폼 레이아웃도 손쉽게 만들 수 있어요.
<template>
<d-form layout="vertical" :data="formModel">
<d-row :gutter="16">
<d-col :span="7">
<d-form-item field="name" label="Name" help-tips="This is the plan name.">
<d-input v-model="formModel.name" />
</d-form-item>
</d-col>
<d-col :span="7">
<d-form-item field="select" label="Select">
<d-select v-model="formModel.select" :options="selectOptions" />
</d-form-item>
</d-col>
<d-col :span="7">
<d-form-item field="multiSelect" label="Multiple Select">
<d-select v-model="formModel.multiSelect" :options="selectOptions" multiple />
</d-form-item>
</d-col>
</d-row>
<d-row :gutter="16">
<d-col :span="7">
<d-form-item field="executionDay" label="Execution day">
<d-checkbox-group v-model="formModel.executionDay" label="Execution day">
<d-checkbox label="Mon" value="Mon" />
<d-checkbox label="Tue" value="Tue" />
<d-checkbox label="Wed" value="Wed" />
<d-checkbox label="Thur" value="Thur" />
<d-checkbox label="Fri" value="Fri" />
<d-checkbox label="Sat" value="Sat" />
<d-checkbox label="Sun" value="Sun" />
</d-checkbox-group>
</d-form-item>
</d-col>
<d-col :span="7">
<d-form-item field="radio" label="Radio">
<d-radio-group direction="row" v-model="formModel.radio">
<d-radio value="0">Manual execution</d-radio>
<d-radio value="1">Daily execution</d-radio>
<d-radio value="2">Weekly execution</d-radio>
</d-radio-group>
</d-form-item>
</d-col>
<d-col :span="7">
<d-form-item field="switch" label="Switch">
<d-switch v-model="formModel.switch"></d-switch>
</d-form-item>
</d-col>
</d-row>
<d-form-operation class="form-demo-form-operation">
<d-button variant="solid">提交</d-button>
<d-button>取消</d-button>
</d-form-operation>
</d-form>
</template>
<script>
import { defineComponent, reactive, ref } from 'vue';
export default defineComponent({
setup() {
const formModel = reactive({
name: '',
select: 'Options2',
multiSelect: ref([]),
executionDay: [],
radio: '0',
switch: true,
});
const selectOptions = reactive(['Options1', 'Options2', 'Options3']);
return { formModel, selectOptions };
},
});
</script>
:::
크기 조절
:::demo size 속성으로 폼 안의 모든 컨트롤 크기를 조절할 수 있어요.
<template>
<div class="form-demo-set-form-size">
尺寸:
<d-radio-group direction="row" v-model="formSize">
<d-radio :value="item.value" v-for="item in sizeTypeList" :key="item.value">
{{ item.label }}
</d-radio>
</d-radio-group>
</div>
<d-form :data="formModel" :size="formSize">
<d-form-item field="name" label="Name">
<d-input v-model="formModel.name" placeholder="请输入" />
</d-form-item>
<d-form-item field="search" label="Search">
<d-search v-model="formModel.search" placeholder="请输入"></d-search>
</d-form-item>
<d-form-item field="inputNumber" label="InputNumber">
<d-input-number v-model="formModel.inputNumber" placeholder="请输入"></d-input-number>
</d-form-item>
<d-form-item field="description" label="Description">
<d-textarea v-model="formModel.description" />
</d-form-item>
<d-form-item field="select" label="Select">
<d-select v-model="formModel.select" :options="selectOptions" />
</d-form-item>
<d-form-item field="autoComplete" label="AutoComplete">
<d-auto-complete :source="source" v-model="formModel.autoComplete"></d-auto-complete>
</d-form-item>
<d-form-item field="radio" label="Radio">
<d-radio-group direction="row" v-model="formModel.radio">
<d-radio value="0">Manual execution</d-radio>
<d-radio value="1">Daily execution</d-radio>
<d-radio value="2">Weekly execution</d-radio>
</d-radio-group>
</d-form-item>
<d-form-item field="switch" label="Switch">
<d-switch v-model="formModel.switch"></d-switch>
</d-form-item>
<d-form-item field="executionDay" label="Execution day">
<d-checkbox-group v-model="formModel.executionDay" label="Execution day" direction="row">
<d-checkbox label="Mon" value="Mon" />
<d-checkbox label="Tue" value="Tue" />
<d-checkbox label="Wed" value="Wed" />
<d-checkbox label="Thur" value="Thur" />
<d-checkbox label="Fri" value="Fri" />
<d-checkbox label="Sat" value="Sat" />
<d-checkbox label="Sun" value="Sun" />
</d-checkbox-group>
</d-form-item>
<d-form-item field="datePickerPro" label="Date Picker Pro">
<d-date-picker-pro v-model="formModel.datePickerPro"></d-date-picker-pro>
</d-form-item>
<d-form-item field="timeSelect" label="Time Select">
<d-time-select v-model="formModel.timeSelect" />
</d-form-item>
<d-form-item field="timePicker" label="Time Picker">
<d-time-picker v-model="formModel.timePicker" placeholder="请选择时间" />
</d-form-item>
<d-form-operation class="form-demo-form-operation">
<d-button variant="solid">提交</d-button>
<d-button>取消</d-button>
</d-form-operation>
</d-form>
</template>
<script>
import { defineComponent, reactive, ref } from 'vue';
export default defineComponent({
setup() {
const sizeTypeList = [
{
label: 'Small',
value: 'sm',
},
{
label: 'Middle',
value: 'md',
},
{
label: 'Large',
value: 'lg',
},
];
const formSize = ref('md');
let formModel = reactive({
name: '',
search: '',
inputNumber: 1,
description: '',
select: '',
autoComplete: '',
radio: '0',
switch: true,
executionDay: [],
datePickerPro: '',
timeSelect: '',
timePicker: '',
});
const selectOptions = reactive(['Options1', 'Options2', 'Options3']);
const source = ref(['C#', 'C', 'C++']);
return {
sizeTypeList,
formSize,
formModel,
source,
selectOptions,
};
},
});
</script>
<style>
.form-demo-set-form-size {
display: flex;
align-items: center;
margin-bottom: 16px;
}
.form-demo-form-operation > * {
margin-right: 8px;
}
</style>
:::
폼 검증
:::demo
<template>
<d-form ref="formRef" layout="vertical" :data="formData" :rules="rules" :pop-position="['right']">
<d-form-item
field="username"
:rules="[{ required: true, message: '用户名不能为空', trigger: 'blur' }]"
:show-feedback="false"
label="用户名"
>
<d-input v-model="formData.username" />
</d-form-item>
<d-form-item field="userInfo" label="用户信息">
<d-textarea v-model="formData.userInfo"></d-textarea>
</d-form-item>
<d-form-item field="age" label="年龄">
<d-input v-model="formData.age" />
</d-form-item>
<d-form-item field="select" label="Select">
<d-select v-model="formData.select" :options="selectOptions" allow-clear />
</d-form-item>
<d-form-item field="autoComplete" label="AutoComplete">
<d-auto-complete :source="source" v-model="formData.autoComplete"></d-auto-complete>
</d-form-item>
<d-form-item field="radio" label="Radio">
<d-radio-group direction="row" v-model="formData.radio">
<d-radio value="0">Manual execution</d-radio>
<d-radio value="1">Daily execution</d-radio>
<d-radio value="2">Weekly execution</d-radio>
</d-radio-group>
</d-form-item>
<d-form-item field="executionDay" label="Execution day">
<d-checkbox-group v-model="formData.executionDay" label="Execution day" direction="row">
<d-checkbox label="Mon" value="Mon" />
<d-checkbox label="Tue" value="Tue" />
<d-checkbox label="Wed" value="Wed" />
<d-checkbox label="Thur" value="Thur" />
<d-checkbox label="Fri" value="Fri" />
<d-checkbox label="Sat" value="Sat" />
<d-checkbox label="Sun" value="Sun" />
</d-checkbox-group>
</d-form-item>
<d-form-item field="datePickerPro" label="Date Picker Pro">
<d-date-picker-pro v-model="formData.datePickerPro"></d-date-picker-pro>
</d-form-item>
<d-form-item field="rangeDatePickerPro" label="Range Date Picker Pro">
<d-range-date-picker-pro v-model="formData.rangeDatePickerPro"></d-range-date-picker-pro>
</d-form-item>
<d-form-operation class="form-operation-wrap">
<d-button variant="solid" @click="onClick">提交</d-button>
<d-button @click="onClear">清除校验结果</d-button>
<d-button @click="onReset">重置</d-button>
</d-form-operation>
</d-form>
</template>
<script>
import { defineComponent, reactive, ref, watch } from 'vue';
export default defineComponent({
setup() {
const formRef = ref(null);
const formData = reactive({
username: '',
userInfo: '',
age: '',
select: 'Options2',
autoComplete: '',
executionDay: ['Tue'],
radio: '',
datePickerPro: '',
rangeDatePickerPro: ['', ''],
});
const selectOptions = reactive(['Options1', 'Options2', 'Options3']);
const source = ref(['C#', 'C', 'C++']);
const checkAge = (rule, value, callback) => {
if (!value) {
return callback(new Error('年龄不能为空'));
}
setTimeout(() => {
if (value < 18) {
return callback(new Error('年龄不能小于18'));
} else {
callback();
}
}, 1000);
};
const checkRangeDatePickerPro = (rule, value, callback) => {
if (!value || (!value[0] && !value[1])) {
return callback(new Error('请选择日期范围'));
} else if (!value[0]) {
return callback(new Error('请选择开始日期'));
} else if (!value[1]) {
return callback(new Error('请选择结束日期'));
} else {
return callback();
}
};
const rules = {
username: [{ min: 3, max: 6, message: '用户名需不小于3个字符,不大于6个字符', trigger: 'change' }],
userInfo: [{ required: true, message: '用户信息不能为空', trigger: 'blur' }],
age: [{ validator: checkAge }],
select: [{ required: true, message: '请选择', trigger: 'change' }],
autoComplete: [{ required: true, message: '请选择', trigger: 'change' }],
executionDay: [{ type: 'array', required: true, message: '请至少选择一个执行时间', trigger: 'change' }],
radio: [{ required: true, message: '请选择', trigger: 'change' }],
datePickerPro: [{ type: 'object', required: true, message: '请选择日期', trigger: 'change' }],
rangeDatePickerPro: [
{ validator: checkRangeDatePickerPro },
{ type: 'array', required: true, message: '请选择日期范围', trigger: 'change' },
],
};
const onClick = () => {
formRef.value.validate((isValid, invalidFields) => {
console.log(isValid);
console.log(invalidFields);
});
};
const onClear = () => {
formRef.value.clearValidate();
};
const onReset = () => {
formRef.value.resetFields();
};
return { formRef, formData, selectOptions, source, rules, onClick, onClear, onReset };
},
});
</script>
<style>
.form-operation-wrap > * {
margin-right: 8px;
}
</style>
:::
Form 파라미터
| 파라미터 | 타입 | 기본값 | 설명 | Demo 링크 |
|---|---|---|---|---|
| data | object |
{} | 필수, 폼 데이터 | 기본 사용법 |
| layout | Layout | 'horizontal' | 선택, 폼 배열 방식 설정 | 세로 배치 |
| label-size | LabelSize | 'md' | 선택, label 너비 설정. 기본 100px, sm은 80px, lg는 150px | 폼 스타일 |
| label-align | LabelAlign | 'start' | 선택, 가로 레이아웃에서 label 정렬 방식 설정 | 폼 스타일 |
| rules | FormRules | -- | 선택, 폼 검증 규칙 설정 | 폼 검증 |
| message-type | MessageType | 'popover' | 선택, 검증 메시지 표시 방식 설정 | |
| pop-position | PopPosition | ['right','bottom'] | 선택, 메시지가 popover로 표시될 때 popover 팝업 방향 | |
| validate-on-rule-change | boolean |
false | 선택, rules 변경 후 즉시 검증 1회 트리거 여부 | |
| show-feedback | boolean |
false | 선택, 검증 결과 피드백 아이콘 표시 여부 | |
| disabled | boolean |
false | 선택, 해당 폼 내 모든 컴포넌트 비활성화 여부 | |
| size | FormSize | -- | 선택, 해당 폼 내 컴포넌트 크기 제어 | |
| hide-required-mark | boolean |
false | 선택, 모든 폼 항목의 필수 표시 숨김 여부 | |
| style-type | StyleType | 'default' | 선택, 폼을 회색 폼으로 설정 | |
| append-to-body-scroll-strategy | AppendToBodyScrollStrategy | 'reposition' | 선택, 메시지가 popover로 표시될 때 스크롤 시 popover 처리 전략. 기본 전략은 호스트를 따라 이동, close는 스크롤 시 닫기 |
|
| require-position | string |
'left' | 선택, 필수 표시 위치. 값은 left 또는 right |
Form 이벤트
| 이벤트명 | 콜백 파라미터 | 설명 |
|---|---|---|
| validate | Function(field: string, isValid: boolean, message: string) => void |
폼 항목 검증 후 트리거 |
Form 메서드
| 메서드명 | 타입 | 설명 | Demo 링크 |
|---|---|---|---|
| validate | (callback?: FormValidateCallback) => Promise |
폼 검증 함수 | 폼 검증 |
| validateFields | (fields: string[], callback?: FormValidateCallback) => Promise |
지정 필드 검증 | |
| resetFields | (fields: string[]) => void |
폼 항목 값을 초기화하고 검증 결과 제거 | |
| clearValidate | (fields: string[]) => void |
검증 결과 제거. 파라미터는 제거할 폼 항목 field, 기본값은 전체 제거 |
Form 슬롯
| 슬롯명 | 설명 |
|---|---|
| default | 폼 전체 콘텐츠 래핑 |
FormItem 파라미터
| 파라미터 | 타입 | 기본값 | 설명 | Demo 링크 |
|---|---|---|---|---|
| label | string |
'' | 선택, 폼 항목 이름 | 기본 사용법 |
| field | string |
'' | 선택, 폼 검증 시 검증할 필드 지정. 폼 검증 시 필수 속성 | 기본 사용법 |
| required | boolean |
false | 선택, 폼 옵션 필수 여부 | |
| rules | FormRuleItem | FormRuleItem[] | -- | 선택, 폼 항목 검증 규칙 | 폼 검증 |
| message-type | MessageType | -- | 선택, 부모 컴포넌트 message-type 파라미터와 용법 동일. 부모보다 우선순위가 높으며, 기본값은 부모 컴포넌트 값 상속 |
|
| pop-position | PopPosition | -- | 선택, 부모 컴포넌트 pop-position 파라미터와 용법 동일. 부모보다 우선순위가 높으며, 기본값은 부모 컴과 값 상속 |
|
| show-feedback | boolean |
-- | 선택, 검증 결과 피드백 아이콘 표시 여부. 부모보다 우선순위가 높으며, 기본값은 부모 컴포넌트 값 상속 | |
| help-tips | string |helptips |
'' | 선택, 폼 항목 도움말 안내 힌트 콘텐츠. 빈 문자열은 힌트 콘텐츠 미설정. 객체 타입 파라미터로 힌트 콘텐츠 스타일, 펼침 방향 등 커스텀 가능 | 기본 사용법 |
| extra-info | string |
'' | 선택, 첨부 정보. 주로 폼 옵션 설명 보충용 | 기본 사용법 |
| feedback-status | FeedbackStatus | -- | 선택, 현재 control 상태 피드백 수동 지정 |
FormItem 메서드
| 메서드명 | 타입 | 설명 |
|---|---|---|
| resetField | () => void |
폼 항목 값을 초기화하고 검증 결과 제거 |
| clearValidate | () => void |
검증 결과 제거 |
| validate | (trigger: string) => Promise |
폼 검증 함수 |
FormItem 슬롯
| 슬롯명 | 설명 |
|---|---|
| default | 개별 폼 항목의 입력 컨트롤 래핑 |
| label | 라벨 위치에 표시할 콘텐츠 |
| extraInfo | 폼 항목 추가 정보 커스텀 |
Form 타입 정의
Layout
type Layout = 'horizontal' | 'vertical';
LabelSize
type LabelSize = 'sm' | 'md' | 'lg';
LabelAlign
type LabelAlign = 'start' | 'center' | 'end';
FormRules
type FormRules = Partial<Record<string, Array<FormRuleItem>>>;
MessageType
type MessageType = 'popover' | 'text' | 'none';
PopPosition
type PopPosition =
| 'top'
| 'right'
| 'bottom'
| 'left'
| 'top-start'
| 'top-end'
| 'right-start'
| 'right-end'
| 'bottom-start'
| 'bottom-end'
| 'left-start'
| 'left-end';
FormSize
type FormSize = 'sm' | 'md' | 'lg';
FormValidateCallback
ValidateFieldsError 타입은 async-validator를 참고하세요.
type FormValidateCallback = (isValid: boolean, invalidFields?: ValidateFieldsError) => void;
StyleType
type StyleType = 'default' | 'gray';
AppendToBodyScrollStrategy
type AppendToBodyScrollStrategy = 'close' | 'reposition';
FormItem 타입 정의
FormRuleItem
RuleItem 타입은 async-validator를 참고하세요.
interface FormRuleItem extends RuleItem {
trigger?: Array<string>;
}
FeedbackStatus
type FeedbackStatus = 'success' | 'error' | 'pending';
HelpTips
interface HelpTips {
content: string; // 提示内容
position?: PopPosition[]; // 展开方向
trigger?: 'hover' | 'click'; // 触发方式
popType?: string; // 提示类型
}
더 알아보기 (Learn more)
Form 컴포넌트를 활용하면 데이터 수집부터 검증, 제출까지 하나의 폼에서 깔끔하게 처리할 수 있어요. rules 옵션을 조합하면 다양한 유효성 검사 시나리오도 손쉽게 구성할 수 있어요.