バリデーションルール
15.2.1. クライアント検証戦略
クライアント側のバリデーションはリアルタイム検証と送信時検証の2つの戦略に区分します。
| 戦略 | トリガータイミング | 適用対象 |
|---|---|---|
| リアルタイム検証 | @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クラスを追加して視覚的フィードバックを提供します。 - エラーメッセージは日本語で作成し、ユーザーが理解できる具体的な案内を含めます。