Vuetify3でVeeValidate4を利用する方法
はじめに
皆さんVeeValidateは利用されているでしょうか?
VeeValidateは、Vue.jsのバリデーションライブラリです。入力値の追跡、検証、エラー、送信などを処理することができます。
Vuetify2でもVuetify3でも公式でVeeValidateを使った実装例を紹介していますので、Vuetifyと合わせて利用されている方も多いかと思います。
ただ、Vuetify2でVeeValidate3を使っていた人が、Vuetify3公式のVeeValidate4を使った実装例をみたときに、こう思ったはずです。
「使い方がぜんぜん違うやんけ!」と…
Vuetify2とVeeValidate3
まずはVuetify2の公式で紹介されているVeeValidate3の使い方です。
長いのでv-text-fieldタグは一つにしていますが、以下のような実装例が紹介されています。
<script>
import { required, email } from 'vee-validate/dist/rules'
import { extend, ValidationObserver, ValidationProvider, setInteractionMode } from 'vee-validate'
setInteractionMode('eager')
extend('required', {
...required,
message: '{_field_} can not be empty',
})
extend('email', {
...email,
message: 'Email must be valid',
})
export default {
components: {
ValidationProvider,
ValidationObserver,
},
data: () => ({
email: '',
}),
methods: {
submit () {
this.$refs.observer.validate()
},
clear () {
this.email = ''
this.$refs.observer.reset()
},
},
}
</script>
<template>
<v-app>
<v-sheet
class="pa-4"
width="540"
>
<validation-observer
ref="observer"
v-slot="{ invalid }"
>
<form @submit.prevent="submit">
<validation-provider
v-slot="{ errors }"
name="email"
rules="required|email"
>
<v-text-field
v-model="email"
:error-messages="errors"
label="E-mail"
required
></v-text-field>
</validation-provider>
<v-btn
class="mr-4"
type="submit"
:disabled="invalid"
>
submit
</v-btn>
<v-btn @click="clear">
clear
</v-btn>
</form>
</validation-observer>
</v-sheet>
</v-app>
</template>
ValidationProvider(validation-provider)とValidationObserver(validation-observer)がVeeValidate3の要です。
ValidationProviderで個々のフィールドのバリデーションのルールの管理やエラーの取得をし、ValidationObserverでValidationProviderによるバリデーションの完了状況を取得しています。
バリデーションのルールはValidationProviderのrules属性でパイプ区切りで設定します。rules属性で利用できるルールはVeeValidate3で用意されている既存ルールの他、extend関数で拡張したルールや独自ルールを実装して利用することもできます。
「setInteractionMode('eager')」はバリデーションの実行タイミングを設定する関数です。この実装例のように引数を'eager'とした場合、エラーが出ていない状態ではフォーカスが外れたらバリデーションが実行されて、エラーが出ている状態では入力するたびにバリデーションが実行されるeagerモードとなります。
ちょっと文章だけでは分かりづらいと思うで、以下にアニメーションを用意しました。
(自分のタイピング遅さに驚く…)
eagerモードでは入力している途中でエラーが出て「まだ入力している途中でしょうが!」となることもありませんし、エラーを修正したのにエラーが出たままとなって「このフィールド、なんでまだ怒ってんの!」となることもありません。
いい感じ!
Vuetify3とVeeValidate4
次にVuetify3の公式で紹介されているVeeValidate4の使い方です。
こちらも長いのでv-text-fieldタグは一つにしていますが、以下のような実装例が紹介されています。
<script setup lang="ts">
import { useField, useForm } from 'vee-validate'
const { handleSubmit, handleReset } = useForm({
validationSchema: {
email (value: any) {
if (/^[a-z.-]+@[a-z.-]+\.[a-z]+$/i.test(value)) return true
return 'Must be a valid e-mail.'
},
},
})
const email = useField('email')
const submit = handleSubmit(values => {
alert(JSON.stringify(values, null, 2))
})
</script>
<template>
<v-app>
<v-sheet
class="pa-4"
width="540"
location="top left"
>
<form @submit.prevent="submit">
<v-text-field
v-model="email.value.value"
:error-messages="email.errorMessage.value"
label="E-mail"
></v-text-field>
<v-btn
class="me-4"
type="submit"
>
submit
</v-btn>
<v-btn @click="handleReset">
clear
</v-btn>
</form>
</v-sheet>
</v-app>
</template>
VeeValidate3と使い方がぜんぜん違うやんけ!
落ち着いてみていきましょう。
VeeValidate3までは、ValidationObserverとValidationProviderでバリデーションが必要なフィールド、フォームのタグを挟み込むという感じの使い方でしたが、この実装例ではuseField関数でバリデーション可能なリアクティブな変数を作成し、useForm関数の引数でvalidationSchemaプロパティにバリデーションを定義して、戻り値でバリデーションを管理するために必要なFormContextインターフェースで定義されたメソッドを取得するという感じの使い方になっています。
これはこれでtemplateタグの中がシンプルになって良さそうです。
ただ、この実装例では入力するたびにバリデーションが実行されます。
動きとしては以下のような感じです。
この動きだと自分は「まだ入力している途中でしょうが!」と言いたくなります…
VeeValidate4をVeeValidate3のように使う
VeeValidate4の使い方はVuetify3の公式で紹介されている方法だけではありません。
VeeValidate4にもValidationObserver、ValidationProviderのようなコンポーネントが用意されています。
それがFormとFieldです。FormとFieldを使った実装例は以下のようになります。
<script setup lang="ts">
import { ref } from 'vue';
import { Field, Form, defineRule, type GenericObject } from 'vee-validate';
import { required } from "@vee-validate/rules";
defineRule("required", (value: string, params: string): boolean|string => {
if (required(value)) {
return true;
} else {
return `${params} can not be empty`;
}
});
defineRule("email", (value: string): boolean|string => {
if (/^[a-z.-]+@[a-z.-]+\.[a-z]+$/i.test(value)) {
return true;
} else {
return 'Email must be valid';
}
});
const emailValue = ref("");
const onSubmit = (values: GenericObject) => {
alert(JSON.stringify(values, null, 2));
};
</script>
<template>
<v-app>
<v-sheet
class="pa-4"
width="540"
location="top left"
>
<Form
v-slot="{ meta, handleReset }"
@submit="onSubmit"
>
<Field
v-slot="{ errors }"
name="email"
v-model="emailValue"
rules="required:Email|email"
>
<v-text-field
label="E-mail"
v-model="emailValue"
:error-messages="errors"
></v-text-field>
</Field>
<v-btn
class="me-4"
type="submit"
:disabled="!meta.valid"
>
submit
</v-btn>
<v-btn
@click="handleReset"
>
clear
</v-btn>
</Form>
</v-sheet>
</v-app>
</template>
まぁ…これでもVeeValidate3のときとは違いますが、バリデーションのルールをrules属性にパイプ区切りで設定するのは同じになりましたし、extend関数の代わりにdefineRule関数が利用できるので、使用感はVeeValidate3に近づきました。
ここで、この実装例の動きを見てみましょう。
この実装例でも、入力するたびにバリデーションが実行されます。
VeeValidate3の実装例のようなeagerモードの動きとするにはどうすれば…
VeeValidate3のときにあった「setInteractionMode('eager')」は何処に…
VeeValidate4でeagerモードの動きをさせる方法
結論から言いますと、現時点(2024/2/8)ではsetInteractionMode('eager')のような設定方法はありません。
これは、Vue3のVNode APIで、ディレクティブを識別する方法がないため、v-modelを含むノードを見つけることができないことに起因しているようです。
(これは…しょうがないね…)
FieldコンポーネントのPropsには、validateOnBlur、validateOnChange、validateOnInput、validateOnModelUpdateがあり、どれもどのイベントのタイミングでバリデーションをかけるかというもので、boolean型で設定します。
単純に、これらの組み合わせでいけそうな気もしますが、「エラーが出ているか」で動作を変える工夫が必要となります。
では、どうするか…
いくつか解法はありそうでしたが、自分の場合は上記Propsは利用せず、以下のような方法にしました。
<script setup lang="ts">
import { ref } from 'vue';
import { Field, Form, defineRule, type GenericObject } from 'vee-validate';
import { required } from "@vee-validate/rules";
defineRule("required", (value: string, params: string): boolean|string => {
if (required(value)) {
return true;
} else {
return `${params} can not be empty`;
}
});
defineRule("email", (value: string): boolean|string => {
if (/^[a-z.-]+@[a-z.-]+\.[a-z]+$/i.test(value)) {
return true;
} else {
return 'Email must be valid';
}
});
const emailValue = ref("");
const onSubmit = (values: GenericObject) => {
alert(JSON.stringify(values, null, 2));
};
</script>
<template>
<v-app>
<v-sheet
class="pa-4"
width="540"
location="top left"
>
<Form
v-slot="{ meta, handleReset }"
@submit="onSubmit"
>
<Field
v-slot="{ errors, handleChange }"
name="email"
v-model="emailValue"
rules="required:Email|email"
>
<v-text-field
label="E-mail"
:model-value="emailValue"
@update:model-value="
(e: string) => handleChange(e, errors.length > 0)
"
@blur="handleChange"
:error-messages="errors"
></v-text-field>
</Field>
<v-btn
class="me-4"
type="submit"
:disabled="!meta.valid"
>
submit
</v-btn>
<v-btn
@click="handleReset"
>
clear
</v-btn>
</Form>
</v-sheet>
</v-app>
</template>
解法としては簡単で、FieldコンポーネントのhandleChangeメソッドでバリデーションが動くタイミングを変更しています。
eagerモードの動きとしたい場合には、@update:model-valueと@blurでhandleChangeメソッドを呼びます。ただし、@update:model-valueのほうでは、handleChangeメソッドの第二引数(shouldValidate)で、エラーが出ているかの判定結果を渡して、エラーがなければ、handleChangeメソッドでバリデーションが動くようにしています。
では、この実装例での動きを見てみましょう。
VeeValidate4でもVeeValidate3のeagerモードのときと同じ動きにすることができました。
いい感じ!
まとめ
以上がVuetify3でVeeValidate4を利用する方法になります。
最終的なソースコードは以下に上げていますので、参考としてください。
参考文献
この記事は以下の情報を参考にして執筆しました。
Discussion