유효성 검증 규칙
15.2.1. 클라이언트 검증 전략
클라이언트 측 유효성 검증은 실시간 검증과 제출 시 검증 두 가지 전략으로 구분합니다.
| 전략 | 트리거 시점 | 적용 대상 |
|---|---|---|
| 실시간 검증 | @blur 또는 watch | 이메일 형식, 비밀번호 강도, 필수 필드 |
| 제출 시 검증 | @submit.prevent | 전체 폼 일괄 검증, 필드 간 교차 검증 |
- 사용자 경험을 위해 실시간 검증을 기본 전략으로 사용합니다.
- 필드 간 의존성이 있는 검증(비밀번호 확인 등)은 제출 시 검증과 병행합니다.
@input이벤트로의 실시간 검증은 과도한 호출을 유발하므로 사용하지 않습니다.@blur이벤트를 사용합니다.
15.2.2. 필수/형식/범위 검증
검증 유형은 다음 3가지로 분류하며, 각 유형에 맞는 검증 함수를 작성합니다.
| 유형 | 설명 | 예시 |
|---|---|---|
| 필수 검증 | 값의 존재 여부 | 빈 문자열, null, undefined 체크 |
| 형식 검증 | 값의 패턴 일치 | 이메일, 전화번호, URL 형식 |
| 범위 검증 | 값의 크기/길이 제한 | 최소 8자, 최대 100자, 1~999 범위 |
typescript
// src/utils/validators.ts
export function required(value: string): string | null {
return value.trim() === '' ? '필수 입력 항목입니다.' : null
}
export function email(value: string): string | null {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return pattern.test(value) ? null : '올바른 이메일 형식이 아닙니다.'
}
export function minLength(min: number) {
return (value: string): string | null => {
return value.length < min ? `최소 ${min}자 이상이어야 합니다.` : null
}
}
export function maxLength(max: number) {
return (value: string): string | null => {
return value.length > max ? `최대 ${max}자까지 입력할 수 있습니다.` : null
}
}- 검증 함수는 에러가 있으면 에러 메시지 문자열을, 유효하면
null을 반환합니다. - 범위 검증은 고차 함수(Higher-Order Function)로 작성하여 임계값을 주입합니다.
15.2.3. 커스텀 규칙
재사용 가능한 검증 로직은 Composable로 분리합니다.
typescript
// src/composables/useFormValidation.ts
import { ref, type Ref } from 'vue'
type Validator = (value: string) => string | null
export function useFormValidation<T extends Record<string, string>>(
form: Ref<T>,
rules: Partial<Record<keyof T, Validator[]>>
) {
const errors = ref<Partial<Record<keyof T, string>>>({})
function validateField(field: keyof T) {
const fieldRules = rules[field]
if (!fieldRules) return
for (const rule of fieldRules) {
const error = rule(form.value[field])
if (error) {
errors.value[field] = error
return
}
}
delete errors.value[field]
}
function validateAll(): boolean {
for (const field of Object.keys(rules) as (keyof T)[]) {
validateField(field)
}
return Object.keys(errors.value).length === 0
}
return { errors, validateField, validateAll }
}비동기 검증(중복 확인 등)은 별도의 비동기 검증 함수로 처리합니다.
typescript
async function checkDuplicateEmail(email: string): Promise<string | null> {
const { isDuplicate } = await api.get(`/users/check-email?email=${email}`)
return isDuplicate ? '이미 사용 중인 이메일입니다.' : null
}- 비동기 검증은
@blur시점에만 실행합니다. - 네트워크 요청이 포함되므로 디바운스를 적용하는 것을 권장합니다.
15.2.4. 에러 메시지 표시
검증 에러는 해당 필드 하단에 인라인으로 표시합니다.
vue
<script setup lang="ts">
import { ref } from 'vue'
import { useFormValidation } from '@/composables/useFormValidation'
import { required, email, minLength } from '@/utils/validators'
const form = ref({ email: '', password: '' })
const { errors, validateField, validateAll } = useFormValidation(form, {
email: [required, email],
password: [required, minLength(8)],
})
</script>
<template>
<form @submit.prevent="validateAll() && handleSubmit()">
<div :class="{ 'has-error': errors.email }">
<input
v-model="form.email"
type="email"
@blur="validateField('email')"
/>
<p v-if="errors.email" class="error-message">{{ errors.email }}</p>
</div>
<div :class="{ 'has-error': errors.password }">
<input
v-model="form.password"
type="password"
@blur="validateField('password')"
/>
<p v-if="errors.password" class="error-message">{{ errors.password }}</p>
</div>
</form>
</template>- 에러 메시지는
v-if로 조건부 렌더링합니다. - 에러 상태의 필드에는
has-error클래스를 추가하여 시각적 피드백을 제공합니다. - 에러 메시지는 한국어로 작성하며, 사용자가 이해할 수 있는 구체적인 안내를 포함합니다.