🐲

Vuetify3でVeeValidate4を利用する方法

はじめに

皆さんVeeValidateは利用されているでしょうか?
VeeValidateは、Vue.jsのバリデーションライブラリです。入力値の追跡、検証、エラー、送信などを処理することができます。
Vuetify2でもVuetify3でも公式でVeeValidateを使った実装例を紹介していますので、Vuetifyと合わせて利用されている方も多いかと思います。
ただ、Vuetify2でVeeValidate3を使っていた人が、Vuetify3公式のVeeValidate4を使った実装例をみたときに、こう思ったはずです。
「使い方がぜんぜん違うやんけ!」と…

Vuetify2とVeeValidate3

まずはVuetify2の公式で紹介されているVeeValidate3の使い方です。
長いのでv-text-fieldタグは一つにしていますが、以下のような実装例が紹介されています。

App.vue
<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モードとなります。
ちょっと文章だけでは分かりづらいと思うで、以下にアニメーションを用意しました。
(自分のタイピング遅さに驚く…)
VeeValidate3サンプル
eagerモードでは入力している途中でエラーが出て「まだ入力している途中でしょうが!」となることもありませんし、エラーを修正したのにエラーが出たままとなって「このフィールド、なんでまだ怒ってんの!」となることもありません。
いい感じ!

Vuetify3とVeeValidate4

次にVuetify3の公式で紹介されているVeeValidate4の使い方です。
こちらも長いのでv-text-fieldタグは一つにしていますが、以下のような実装例が紹介されています。

App.vue
<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サンプル
この動きだと自分は「まだ入力している途中でしょうが!」と言いたくなります…

VeeValidate4をVeeValidate3のように使う

VeeValidate4の使い方はVuetify3の公式で紹介されている方法だけではありません。
VeeValidate4にもValidationObserver、ValidationProviderのようなコンポーネントが用意されています。
それがFormとFieldです。FormとFieldを使った実装例は以下のようになります。

App.vue
<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に近づきました。
ここで、この実装例の動きを見てみましょう。
VeeValidate4サンプル2
この実装例でも、入力するたびにバリデーションが実行されます。
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は利用せず、以下のような方法にしました。

App.vue
<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サンプル3
VeeValidate4でもVeeValidate3のeagerモードのときと同じ動きにすることができました。
いい感じ!

まとめ

以上がVuetify3でVeeValidate4を利用する方法になります。
最終的なソースコードは以下に上げていますので、参考としてください。
https://github.com/y-kashima-iandc/vuetify-textfield-with-vee-validate

参考文献

この記事は以下の情報を参考にして執筆しました。

株式会社アイアンドシー Tech Blog

Discussion