🌟
Vuelidate 2の悩んだところのメモ
前提
create-vueで作成したVue3 + Vuelidate 2の組み合わせで開発するにあたり
公式ドキュメントを読んでもわかりにくかったところ、迷ったところの解決方法を記載する
i18nはエラーメッセージを上書きする
Vuelidate公式のi18nサポートはバリデーターとi18nメッセージを関連付ける
同じバリデーターでも異なるi18nメッセージを表示したいことがあった
helpers.withMessageの第1引数を文字列を返す関数にする
vue-i18n-nextでi18nメッセージを返す
サンプルコード
const { t } = useI18n()
const rules = computed(() => ({
name: {
required: helpers.withMessage(() => t('required_key'), required)
}
}))
useVuelidateのrule(第1引数のcomputed)は再評価されないようにする
再評価時に初期化を行うようで、$clearExternalResultsが正常に動作しないことがあった
再評価されないようにコードを記述する
(不具合がなくても余計な処理を行われないようにする)
サンプルコード
type Props = {
isEnabled: boolean
}
const props = defineProps<Props>()
const { isEnabled: isEnabledRef } = toRef(props, 'isEnabled')
const rules = computed(() => ({
name: {
// NG: props.isEnabledが変更されるたびにcomputedが再評価される
requiredIf: helpers.withMessage('必須入力です', requiredIf(props.isEnabled))
// OK1: 関数内でpropsを参照する
requiredIf: helpers.withMessage('必須入力です', requiredIf(() => props.isEnabled))
// OK2: propsをrefに変換する
requiredIf: helpers.withMessage('必須入力です', requiredIf(isEnabledRef))
}
}))
標準バリデーター(Built-in Validator)のコール方法
required, numeric, maxLengthなどの用意されているバリデーターをコードにより呼び出す方法
$validatorメソッドをコールする、第2,3の引数は内部で使用している値を渡す
サンプルコード
const maxLength10 = maxLength(10)
const rules = computed(() => ({
name: {
maxLength: helpers.withMessage('数値かつ10文字以内で入力してください', (value: string, siblingState: unknown, vm: unknown) => {
// propsにより検証を行わないことを想定したガード節
if (!props.isEnabled) return true
// 標準バリデーターを呼び出す(数値と文字数の検証)
return numeric.$validator(value, siblingState, vm) && maxLength10.$validator(value, siblingState, vm)
})
},
}))
増減する入力フォーム + APIの検証エラー
ドキュメントから下記は理解できたが、組み合わせ時がわからなかった
・増減する入力フォームの検証はNested Validationsを使用する
・APIの検証エラーはexternalResultsを使用する
「増減する入力フォーム + APIの検証エラー」の対応
APIの検証エラーはexternalResultsに変換し、子コンポーネントに渡す
子コンポーネントに渡したexternalResultsは再検証時に親コンポーネントに渡す
サンプルコード
VueUseのuseVModelsを使用しています
// UserFormType.ts
export type UserForm = {
users: {
name: string
email: string
externalResults?: {
name: string[]
email: string[]
}
}[]
}
// UserForm.vue(親コンポーネント)
<template>
<form @submit.prevent="submitHandler">
<template v-for="(user, index) in form.users" :key="index">
<UserFormItem
v-model:name="user.name"
v-model:email="user.email"
:external-results="user.externalResults"
></UserFormItem>
<button>Send API</button>
</template>
</form>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import { useVuelidate } from '@vuelidate/core'
import UserFormItem from '@/components/UserFormItem.vue'
import type { UserForm } from '@/components/UserFormType'
const form = reactive<UserForm>({
users: [
{ name: 'yamada', email: 'yamada@example.com' },
{ name: 'taro', email: 'taro@example.com' },
]
})
const v$ = useVuelidate()
const submitHandler = async () => {
// 検証前に子コンポーネントのexternalResultsをクリアする
// v$.value.$clearExternalResultsは子コンポーネントに使用できない
form.users = form.users.map((user) => ({...user, externalResults: undefined }))
// 検証(Nested Validationで子コンポーネントの検証を実行)
const hasError = !(await v$.value.$validate())
if (hasError) return
// APIをコール
// await fetch('https://example.com/users')
// APIで検証エラーがある場合は、レスポンスをexternalResultsに変換し、フォームに入れる
// 2つめのユーザの入力欄でエラーがある想定のコード
form.users[1].externalResults = {
name: ['重複しています', '不正な値です'],
email: ['重複しています'],
}
}
</script>
// UserFormItem.vue(子コンポーネント)
<template>
<label>名前<input type="text" v-model="name" /></label>
<p v-for="error in v$.name.$errors" :key="error.$uid">{{ error.$message }}</p>
<label>メール<input type="text" v-model="email" /></label>
<p v-for="error in v$.email.$errors" :key="error.$uid">{{ error.$message }}</p>
</template>
<script setup lang="ts">
import { computed, toRef } from 'vue'
import { helpers, required, email as emailValidate } from '@vuelidate/validators'
import { useVModels } from '@vueuse/core'
import { useVuelidate } from '@vuelidate/core'
import type { UserForm } from '@/components/UserFormType'
type Props = {
name: UserForm['users']['0']['name']
email: UserForm['users']['0']['email']
externalResults?: UserForm['users']['0']['externalResults']
}
const props = withDefaults(defineProps<Props>(), {
// Props未指定時(undefined時)はエラーなし(空配列)とする
externalResults: () => ({
name: [],
email: [],
})
})
type Emit = {
(e: 'update:name', value: UserForm['users']['0']['name']): void
(e: 'update:email', value: UserForm['users']['0']['email']): void
}
const emit = defineEmits<Emit>()
const { name, email } = useVModels(props, emit)
const rules = computed(() => ({
name: {
required: helpers.withMessage('必須入力です', required)
},
email: {
required: helpers.withMessage('email形式で入力してください', emailValidate)
},
}))
// propsのexternalResultsをrefに変換する
const externalResults = toRef(props, 'externalResults')
const v$ = useVuelidate(rules, { name, email }, { $externalResults: externalResults })
</script>
Discussion