Skip to content

유효성 검증 규칙

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 클래스를 추가하여 시각적 피드백을 제공합니다.
  • 에러 메시지는 한국어로 작성하며, 사용자가 이해할 수 있는 구체적인 안내를 포함합니다.

TIENIPIA QUALIFIED STANDARD