104. Custom Error Messages
npm create vue@latest
동일 오류 메시지 문제 및 해결 방법
- 모든 규칙에 대해 동일한 오류 메시지가 출력되는 문제 인식
validate
파일에서 규칙을 정의하고 오류 메시지를 커스터마이즈하는 방법 탐구
커스텀 오류 메시지 설정
configure
함수를 사용하여 검증 라이브러리의 기본 동작을 설정- 글로벌 검증자에 대한 기본 메시지를 오버라이드
generateMessage
옵션을 화살표 함수로 설정하여 글로벌 검증 함수가false
를 반환할 때 호출
오류 메시지를 위한 컨텍스트 활용
- 컨텍스트 객체를 사용하여 필드 이름, 값, 위반된 규칙에 대한 정보에 접근.
- 각 글로벌 규칙에 대한 메시지를
messages
객체에 추가.
특정 및 일반적인 시나리오를 위한 규칙 복사 및 수정:
excluded
와required
규칙을 복사하여 특정 시나리오에 맞게 수정.countryExcluded
,passwordsMismatch
,tos
와 같은 새로운 규칙 이름으로 별도의 메시지 생성.
폼 컴포넌트의 스키마 데이터 업데이트:
authentication
컴포넌트에서schema
데이터 속성을 업데이트하여 커스텀 규칙 적용.
브라우저에서 테스트:
- 국가 선택, 약관 동의 체크박스, 다른 필수 필드에 대한 오류 메시지 테스트.
- 특정 및 일반적인 시나리오에 맞는 오류 메시지가 정상적으로 출력되는지 확인.
일반적인 오류 메시지와 특별한 경우 처리:
- 동일한 규칙을 복사하여 다른 이름으로 사용함으로써 일반적인 상황과 특별한 경우에 대한 오류 메시지를 동시에 관리.
- 이를 통해 일반적인 메시지는 유지하면서 특정 상황에 맞는 맞춤형 오류 메시지를 제공할 수 있음.
src/components/AppAuth.vue
<script setup lang="ts">
import useModal from '@/stores/modal'
import { storeToRefs } from 'pinia'
import { ref } from 'vue'
const modalStore = useModal()
const { hiddenClass, isOpen: modalVisibility } = storeToRefs(modalStore)
const tab = ref('login')
const schema = ref({
name: 'required|min:3|max:100|alpha_spaces',
email: 'required|min:3|max:100|email',
age: 'required|min_value:18|max_value:100',
password: 'required|min:9|max:100|excluded:password', // password 단어를 입력하면 유효성 검사를 통과할 수 없다.
confirm_password: 'passwords_mismatch:@password', // 비밀번호 입력할 때, 비밀번호 재확인차 입력하는 필드 // 위의 password 부분과 일치하는지 확인함
country: 'required|country_excluded:Antarctica',
tos: 'required'
})
const userData = ref({
country: 'USA'
})
const register = (values: {
age: number
confirm_password: string
country: string
email: string
name: string
password: string
tos: number
}) => {
// VeeForm 안에 VeeField의 모든 유효성 검사를 통과해야지 values 값이 들어온다.
// ex.
// {
// age: 31
// confirm_password: "asdf"
// country: "Germany"
// email: "sdf@sdf.com"
// name: "sfdf"
// password: "asdf"
// tos: "1"
// }
console.log(values)
}
</script>
<template>
<!-- Auth Modal -->
<div class="fixed z-10 inset-0 overflow-y-auto" id="modal" :class="hiddenClass">
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity">
<div class="absolute inset-0 bg-gray-800 opacity-75"></div>
</div>
<!-- This element is to trick the browser into centering the modal contents. -->
<span class="hidden sm:inline-block sm:align-middle sm:h-screen">​</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
>
<!-- Add margin if you want to see some of the overlay behind the modal-->
<div class="py-4 text-left px-6">
<!--Title-->
<div class="flex justify-between items-center pb-4">
<p class="text-2xl font-bold">Your Account</p>
<!-- Modal Close Button -->
<div class="modal-close cursor-pointer z-50" @click="modalVisibility = false">
<i class="fas fa-times"></i>
</div>
</div>
<!-- Tabs -->
<ul class="flex flex-wrap mb-4">
<li class="flex-auto text-center">
<a
class="block rounded py-3 px-4 transition"
:class="{
'hover:text-white text-white bg-blue-600': tab === 'login',
'hover:text-blue-600': tab === 'register'
}"
href="#"
@click.prevent="tab = 'login'"
>
Login
</a>
</li>
<li class="flex-auto text-center">
<a
class="block rounded py-3 px-4 transition"
:class="{
'hover:text-white text-white bg-blue-600': tab === 'register',
'hover:text-blue-600': tab === 'login'
}"
href="#"
@click.prevent="tab = 'register'"
>
Register
</a>
</li>
</ul>
<!-- Login Form -->
<form v-show="tab === 'login'">
<!-- Email -->
<div class="mb-3">
<label class="inline-block mb-2">Email</label>
<input
type="email"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
placeholder="Enter Email"
/>
</div>
<!-- Password -->
<div class="mb-3">
<label class="inline-block mb-2">Password</label>
<input
type="password"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
placeholder="Password"
/>
</div>
<button
type="submit"
class="block w-full bg-purple-600 text-white py-1.5 px-3 rounded transition hover:bg-purple-700"
>
Submit
</button>
</form>
<!-- Registration Form -->
<VeeForm
v-show="tab === 'register'"
:validation-schema="schema"
@submit="register"
:initial-values="userData"
>
<!-- Name -->
<div class="mb-3">
<label class="inline-block mb-2">Name</label>
<VeeField
type="text"
name="name"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
placeholder="Enter Name"
/>
<ErrorMessage class="text-red-600" name="name" />
</div>
<!-- Email -->
<div class="mb-3">
<label class="inline-block mb-2">Email</label>
<VeeField
type="email"
name="email"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
placeholder="Enter Email"
/>
<ErrorMessage class="text-red-600" name="email" />
</div>
<!-- Age -->
<div class="mb-3">
<label class="inline-block mb-2">Age</label>
<VeeField
type="number"
name="age"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
/>
<ErrorMessage class="text-red-600" name="age" />
</div>
<!-- Password -->
<div class="mb-3">
<label class="inline-block mb-2">Password</label>
<VeeField name="password" :bails="false" #default="{ field, errors }">
<input
type="password"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
placeholder="password"
v-bind="field"
/>
<div class="text-red-600" v-for="error in errors" :key="error">
{{ error }}
</div>
</VeeField>
</div>
<!-- Confirm Password -->
<div class="mb-3">
<label class="inline-block mb-2">Confirm Password</label>
<VeeField
type="password"
name="confirm_password"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
placeholder="Confirm Password"
/>
<ErrorMessage class="text-red-600" name="confirm_password" />
</div>
<!-- Country -->
<div class="mb-3">
<label class="inline-block mb-2">Country</label>
<VeeField
as="select"
name="country"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
>
<option value="USA">USA</option>
<option value="Mexico">Mexico</option>
<option value="Germany">Germany</option>
<option value="Antarctica">Antarctica</option>
</VeeField>
<ErrorMessage class="text-red-600" name="country" />
</div>
<!-- TOS -->
<div class="mb-3 pl-6">
<VeeField
type="checkbox"
name="tos"
value="1"
class="w-4 h-4 float-left -ml-6 mt-1 rounded"
/>
<label class="inline-block">Accept terms of service</label>
<ErrorMessage class="text-red-600 block" name="tos" />
</div>
<button
type="submit"
class="block w-full bg-purple-600 text-white py-1.5 px-3 rounded transition hover:bg-purple-700"
>
Submit
</button>
</VeeForm>
</div>
</div>
</div>
</div>
</template>
<style scoped></style>
src/includes/validation.ts
import {
Form as VeeForm,
Field as VeeField,
defineRule,
ErrorMessage,
configure
} from 'vee-validate'
import {
required,
min,
max,
alpha_spaces as alphaSpaces,
email,
min_value as minVal,
max_value as maxVal,
confirmed,
not_one_of as excluded
} from '@vee-validate/rules'
import type { App } from 'vue'
export default {
// install(app: App, options?: { [key: string]: any }) {}
install(app: App) {
app.component('VeeForm', VeeForm)
app.component('VeeField', VeeField)
app.component('ErrorMessage', ErrorMessage)
defineRule('required', required)
defineRule('tos', required)
defineRule('min', min)
defineRule('max', max)
defineRule('alpha_spaces', alphaSpaces)
defineRule('email', email)
defineRule('min_value', minVal)
defineRule('max_value', maxVal)
defineRule('passwords_mismatch', confirmed)
defineRule('excluded', excluded)
defineRule('country_excluded', excluded)
configure({
generateMessage(ctx) {
const messages: { [key: string]: string } = {
required: `The field ${ctx.field} is required.`,
min: `The field ${ctx.field} is too short.`,
max: `The field ${ctx.field} is too long.`,
alpha_spaces: `The field ${ctx.field} may only contain alphabetical characters and space.`,
email: `The field ${ctx.field} must be a valid email.`,
min_value: `The field ${ctx.field} is too low.`,
max_value: `The field ${ctx.field} is too high.`,
excluded: `You are not allowed to use this value for the field ${ctx.field}`,
country_excluded: `Due to restrictions, we do not accept users from this location.`,
passwords_mismatch: `The passwords don't match.`,
tos: `You must accept the Terms of Services.`
}
let message = ''
const name = ctx.rule?.name
if (typeof name === 'string') {
const messagesName = messages[name]
message = messagesName ? messagesName : `The field ${ctx.field} is invalid`
}
return message
}
})
}
}