入力フォームを作成した場合には必ずバリデーション処理を実装する必要があります。バリデーションはユーザが入力フォームから入力した値が要件を満たしているかチェックを行う仕組みです。例えばネット上のサービスを利用する場合にはメールアドレスの入力を求められます。入力時にメールアドレスの形式になっているかどうかチェックを行うのがバリデーションです。

フォームバリデーションには入力値をチェックするだけではなくエラーメッセージの管理、入力したデータの管理、submit処理など含まれ、すべて自分で実装しようとすると非常に大変な作業になります。vee-validateなどのライブラリの力を借りることでフォーム作成の負担を下がることができフォーム以外の処理に時間をかけることができます。ライブラリを利用することで自分で実装することに比べて楽にはなりますが初めて利用する場合は利用方法が難しいと感じるかもしれません。vee-validateのドキュメントを読んだけどわからなかったという人を対象に説明を行っています。

Vue.jsではvee-validateだけではなくVuelidateという別のバリデーションライブラリも存在します。

Vue3環境の構築

Vite環境で動作確認を行うためnpm init vue@latestコマンドを実行したVue.jsプロジェクトの作成を行います。npm init vueコマンドではプロジェクトで利用する機能を選択してインストールすることができます。本文書ではvee-validateの動作確認を行うだけなのですべて”No”を選択しています。


 % npm init vue@latest
Need to install the following packages:
  create-vue@3.6.4
Ok to proceed? (y) y

Vue.js - The Progressive JavaScript Framework

✔ Project name: … vite-vee-valitdate
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / Yes

Scaffolding project in /Users/mac/Desktop/vite-vee-valitdate...

Done. Now run:

  cd vite-vee-valitdate
  npm install
  npm run dev

vee-validateのインストール

npmコマンドを利用してvee-validateのインストールを行います。Vue 3に対応しているのはvee-validateのバージョン4.xです。以下のコマンドを実行することでvee-validateの4.xをインストールすることができます。


 % npm i vee-validate--save

vee-validateのインストールが完了したらpackage.jsonファイルから動作確認で利用したライブラリのバージョンを確認しておきます。


{
  "name": "vite-vee-valitdate",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "vee-validate": "^4.9.3",
    "vue": "^3.3.2"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.2.3",
    "vite": "^4.3.5"
  }
}

vueのバージョンは3.3.2でvee-validateのバージョンは4.9.3であることが確認できます。

Vue 3でvee-validateを利用する方法にはComponentsを利用する方法とCompositon APIを利用する方法の2つの方法があります。Componentsを利用する方が簡単でほとんどのケースではComponentsを利用すべきとドキュメントには記載されていますが本文書ではComposition APIを主に利用してvee-validateの設定方法を確認します。Componentsについても本書の後半に設定方法を記載しています。

vee-validateの基本動作確認

Composition APIを利用して設定を行なっていくためuseField、useFormといった関数を利用します。Composition APIではscript setupでコードを記述していきます。

前準備

バリデーションを行うinput要素を追加するためにApp.vueファイルに以下を記述します。


<script setup></script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
</template>

npm init vueコマンドで作成した場合にmain.jsファイルでassetsのmain.cssファイルのimportを行っているためimport行の削除を行っておきます。


import { createApp } from 'vue';
import App from './App.vue';

createApp(App).mount('#app');

npm run devコマンドを実行して開発サーバを起動してブラウザでアクセスすると下記の画面が表示されます。


 % npm run dev

> vite-vee-valitdate@0.0.0 dev
> vite


  VITE v4.3.5  ready in 452 ms

  ➜  Local:   http://localhost:5173/
初期設定後のページの確認
初期設定後のページの確認

input要素の追加

App.vueファイルにref関数を利用してリアクティブデータnameを定義します。templateタグの中ではinputタグにv-modelで定義したnameの設定を行います。ref関数を利用するためにはimportが必要です。


<script setup>
import { ref } from 'vue';
const name = ref('');
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="name" />
  <p>{{ name }}</p>
</template>

input要素に文字を入力すると{{name}}によりinput要素の下に入力した文字列が表示されます。

input要素の設定
input要素の設定

input要素にv-modelを追加してref関数で定義したnameを設定することでinput要素に入力した文字をブラウザ上に表示させることができました。

はじめてのバリデーション

vee-validateではref関数ではなくuseField関数を利用します。useField関数はvee-validateからimportします。useField関数を利用することでrefを利用して記述したコードを下記のように書き換えることができます。


<script setup>
import { useField } from 'vee-validate';
const { value } = useField('name');
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="value" />
  <p>{{ value }}</p>
</template>

useFieldから戻されるオブジェクトにはvalue以外にもさまざまなプロパティが含まれていますその中のプロパティのvalueを取り出してv-modelに指定します。

useFieldに書き換えてもinput要素に文字を入力するとinput要素の下に入力した文字列が表示されます。

useField関数では入力した値を利用してバリデーションを行うことができます。useFiledの第2引数にバリデーションの関数を追加します。input要素に文字を入力する度に関数が実行され値が入っていない場合にエラーメッセージ”this field is required”を戻しています。valueに値が入っている場合はtrueが戻されます。


<script setup>
import { useField } from 'vee-validate';
const { value } = useField('name', (value) => {
  if (!value) {
    return 'this field is required';
  }
  return true;
});
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="value" />
  <p>{{ value }}</p>
</template>

バリデーションの関数を追加しただけではブラウザ上では何も変化が起こりません。バリデーションのエラーはuseFieldで戻されるオブジェクトに含まれるerrorMessageに保存されます。errorMessageの中身が表示できるようにtemplateタグ側に{{ errorMessage }}を追加しています。


<script setup>
import { useField } from 'vee-validate';
const { value, errorMessage } = useField('name', (value) => {
  if (!value) {
    return 'this field is required';
  }
  return true;
});
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="value" />
  <p>{{ errorMessage }}</p>
</template>

ブラウザで確認するとページを開いた時点では何もエラーは表示されていませんが一度input要素に文字を入力して入力した文字を削除するとvalueの値が空になるのでエラーメッセージが表示されます。

input要素に値が入っていない場合にユーザにエラーメッセージで伝えるというバリデーション機能を実装することができました。

バリデーションを利用してエラーを表示
バリデーションを利用してエラーを表示

バリデーションの処理を別の関数として定義することもできます。isRequiredで定義することで複数のuseFieldで利用することができます。


<script setup>
import { useField } from 'vee-validate';
const isRequired = (value) => (value ? true : 'This field is required');
const { value, errorMessage } = useField('name', isRequired);
</script>

バリデーションを実装することができましたがここでふと疑問を持つ人もいるかと思います。バリデーションのコードは毎回自作しなければならないのだろうか。。。

@vee-validate/rulesライブラリ

vee-validateからバリデーションのルールが@vee-validate/rulesライブラリとして提供されているので利用することができます。@vee-validate/rulesライブラリ以外にもバリデーションライブラリのyup, zodを利用することができます。

@vee-validate/rulesを利用するためにインストールが必要となります。


$  npm install @vee-validate/rules

インストールしたvee-validate/rulesパッケージからrequiredルールをimportしてuseFieldの第二引数に設定を行います。


<script setup>
import { useField } from 'vee-validate';
import { required } from '@vee-validate/rules';
const { value, errorMessage } = useField('name', required);
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="value" />
  <p>{{ errorMessage }}</p>
</template>

これで設定は完了なので一度input要素に文字を入力して入力した文字を削除するとvalueの値が空になるのでエラーメッセージ(name is not valid)が表示されます。

vee-validate/rulesパッケージに登録されているルールについてはドキュメントから確認することができます。

バリデーションルール一覧
バリデーションルール一覧

他のルールを設定してもエラーメッセージはすべて”name is not valid”と表示されます。バリデーションに合わせてメッセージを変えるためGlobal Validatorsの設定を行います。

Global Validatorsの設定

Global Validatorsへの登録方法を確認することができます。Global Validatorsには自分で作成したバリデーション関数を登録することも可能ですがvee-validate/rulesパッケージのバリデーションルールを登録することができます。

App.vueファイルで設定を行います。vee-validateからimportしたdefineRuleを利用して設定を行います。vee-validate/rulesからimportしたrequired関数をdefineRule関数でrequiredとして登録しています。


<script setup>
import { useField, defineRule } from 'vee-validate';
import { required } from '@vee-validate/rules';

defineRule('required', required);

const { value, errorMessage } = useField('name', 'required');
</script>

useFieldで利用する際はdefineRuleの第一引数で指定したrequiredという文字列で設定を行うことができます。defineRuleでrequired以外の名前をつけた場合はその名前をuseFieldで設定します。

Global Validatorsという名前の通りmain.jsに登録をするとどこコンポーネントからも利用することができます。下記のようにmain.jsファイルで登録を行います。


import { createApp } from 'vue';
import App from './App.vue';
import { defineRule } from 'vee-validate';
import { required } from '@vee-validate/rules';

defineRule('required', required);

createApp(App).mount('#app');

main.jsで設定後、App.vueファイルでもrequiredを利用することができます。もしmain.jsでrequiredのバリエーションを登録していない場合にはメッセージ”Unncaught (in promise) Error: No such validator ‘required’ exists.”がブラウザのコンソールに表示されます。


<script setup>
import { useField } from 'vee-validate';

const { value, errorMessage } = useField('name', 'required');
</script>

Global Validatorsを利用してバリデーションを設定することができましたが表示されるエラーメッセージは”email is not valid”のままです。メッセージをバリデーションに合わせて表示するために@vee-validate/i18nを利用します。

@vee-validate/i18nの設定

@vee-validate/i18nを利用するためにインストールを行います。


 % npm install @vee-validate/i18n

まず動作確認としてApp.vueファイルでvee-validate/i18nの設定を行います。


<script setup>
import { useField, configure } from 'vee-validate';
import { localize } from '@vee-validate/i18n';
import en from '@vee-validate/i18n/dist/locale/en.json';

configure({
  generateMessage: localize({
    en,
  }),
});

const { value, errorMessage } = useField('name', 'required');
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="value" />
  <p>{{ errorMessage }}</p>
</template>

メッセージが”The name field is required”になりました。

i18nによるメッセージ
i18nによるメッセージ

表示されるメッセージについてはimportしている”@vee-validate/i18n/dist/locale/en.json”から確認することができます。node_modules下に保存されています。

en.jsonの中身
en.jsonの中身

localeディレクトリの中には日本のja.jsonを含めさまざまな言語のファイルが保存されています。

英語から日本語に変更することも簡単に行えます。enで設定されていたファイル名などをjaに変更してlocalize関数でjaを指定します。


import { useField, configure } from 'vee-validate';
import { localize } from '@vee-validate/i18n';
import ja from '@vee-validate/i18n/dist/locale/ja.json';

configure({
  generateMessage: localize({
    ja,
  }),
});

localize('ja');

const { value, errorMessage } = useField('name', 'required');

設定後動作確認を行うと英語から日本語に変更されていることがわかります。

メッセージの日本語化
メッセージの日本語化

i18nの設定はmain.jsファイルでも行うことができます。


import { createApp } from 'vue';
import App from './App.vue';
import { defineRule, configure } from 'vee-validate';
import { required } from '@vee-validate/rules';
import { localize } from '@vee-validate/i18n';
import ja from '@vee-validate/i18n/dist/locale/ja.json';

defineRule('required', required);

configure({
  generateMessage: localize({
    ja,
  }),
});

localize('ja');

createApp(App).mount('#app');

複数のバリデーションの登録

Global Validatorsへ@vee-validate/rulesのルールを一括で登録したい場合には以下のように行うことができます。


import { createApp } from 'vue';
import App from './App.vue';
import { defineRule, configure } from 'vee-validate';
import { localize } from '@vee-validate/i18n';
import ja from '@vee-validate/i18n/dist/locale/ja.json';
import AllRules from '@vee-validate/rules';

Object.keys(AllRules).forEach((rule) => {
  defineRule(rule, AllRules[rule]);
});

configure({
  generateMessage: localize({
    ja,
  }),
});

localize('ja');

createApp(App).mount('#app');

複数のバリデーションを利用したい場合には”|”で設定することができます。requiredとemailのバリデーションルールを利用しています。


const { value, errorMessage } = useField('name', 'required|email');

メッセージも日本語で表示されます。

複数のバリデーションルールの設定
複数のバリデーションルールの設定

yupのインストール

yupはJavaScriptのバリデーションライブラリでvee-validateでバリデーターして利用することができます。利用するためにはyupのインストールが必要なのでnpmコマンドでインストールを行います。


$ npm install --save yup
yupでエラーメッセージを日本語化したい場合には@vee-validate/i18nは利用できません。@vee-validate/i18nは@vee-validate/rules用のライブラリです。

インストールが完了したらApp.vueファイルでyupのimportを行い、先程設定していたバリデーション関数をyupに変更します。必須項目なのでyup.required()ではと思ってしまうかもしれませんがyup.string().required()のように文字列なのでstring()が必要です。


<script setup>
import { useField } from 'vee-validate';
import * as yup from 'yup';
const { value, errorMessage } = useField('name', yup.string().required());
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="value" />
  <p>{{ errorMessage }}</p>
</template>

yupに変更後にinput要素に文字を入れて、その後文字を削除するとエラーメッセージが表示されます。yupを利用してバリデーションができることが確認できました。

バリデーションライブラリyupを利用した場合のエラー
バリデーションライブラリyupを利用した場合のエラー

ここまでの動作確認でinput要素が1つ存在する場合のvee-validateを利用したバリデーションの方法の基礎を理解することができました。

input要素が1つなのでvalueという名前を利用していましたが複数のinput要素を扱っていくのでvalueとerrorMessageに別名をつけます。

分割代入で別名をつける場合は:(コロン)を利用します。別名をつけても動作は変わりません。


<script setup>
import { useField } from 'vee-validate';
import * as yup from 'yup';
const { value: name, errorMessage: nameError } = useField(
  'name',
  yup.string().required()
);
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="name" />
  <p>{{ nameError }}</p>
</template>

別名をつけても動作することが確認できたので次は複数input要素のある入力フォームの場合にはどのように設定を行なっていくかを確認していきます。

複数のinput要素の動作確認

これまでは1つのinput要素でしたがここで新たにinput要素を1つ追加します。追加したinput要素は必須項目なだけではなくメールアドレスの形式かどうかチェックを行うバリデーションemail()を追加しています。それ以外についてはnameの設定と同じです。useFieldの戻り値に別名をつけることで2つのinput要素に設定を行うことができます。


<script setup>
import { useField } from 'vee-validate';
import * as yup from 'yup';
const { value: name, errorMessage: nameError } = useField(
  'name',
  yup.string().required()
);
const { value: email, errorMessage: emailError } = useField(
  'email',
  yup.string().required().email()
);
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="name" />
  <p>{{ nameError }}</p>
  <input type="text" v-model="email" />
  <p>{{ emailError }}</p>
</template>

ブラウザで確認すると2つのinput要素が表示されます。

2つのinput要素の設定
2つのinput要素の設定

2つ目のinput要素にメールアドレスを入れるため文字列を入力します。入力した文字列がメールアドレスの形式になっていないためエラーが表示されます。

emailによるバリデーション
emailによるバリデーション

john@exmaple.comなどメール形式の文字列を入力するとエラーが表示されなくなります。これまでバリデーションのrequireのみ確認してきましたがemailのバリデーションも問題なく動作することが確認できました。1つ目のinput要素は文字を入力しても何も変化がありまませんが入力した文字を削除した場合にrequiredのバリデーションを設定しているのでメッセージが表示されます。2つのinput要素にそれぞれ異なるバリデーションを設定できることが確認できました。

1つ目のinput要素のバリデーションの確認
1つ目のinput要素のバリデーションの確認

useFormの利用

useFieldを利用してinput要素毎にバリデーションの設定を行えることがわかりました。1つ1つ独立していたバリデーションの設定をスキーマを定義しuseForm関数と一緒に利用することでバリデーションの処理を1つにまとめることができます。スキーマというと何か難しいことを設定するのではないかと思うかもしれませんが一つの入れ物を用意してそこにバリデーションを一括で設定すると考えてください。

まずはバリデーションスキーマの定義を行います。nameとemailのバリデーションの設定を行っています。


const schema = yup.object({
  name: yup.string().required(),
  email: yup
    .string()
    .required()
    .email(),
});

useForm関数を利用して作成したschemaを設定します。


useForm({
  validationSchema: schema,
});
schemaという名前をつけていますが任意の名前をつけることができるので例えばvalidationSchemaという名前をつけても問題ありません。

バリデーションスキーマでバリデーションの定義を行なっているのでuseFieldではバリデーションの関数の定義を設定する必要がなくなります。


const { value: name, errorMessage: nameError } = useField('name');
const { value: email, errorMessage: emailError } = useField('email');

useForm関数を利用すると最終的には下記のようなコードに書き換えることができます。useFieldの第二引数に設定していたバリデーションがスキーマにまとめたためなくなっています。


<script setup>
import { useField, useForm } from 'vee-validate';
import * as yup from 'yup';
const schema = yup.object({
  name: yup.string().required(),
  email: yup.string().required().email(),
});
useForm({
  validationSchema: schema,
});
const { value: name, errorMessage: nameError } = useField('name');
const { value: email, errorMessage: emailError } = useField('email');
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="name" />
  <p>{{ nameError }}</p>
  <input type="text" v-model="email" />
  <p>{{ emailError }}</p>
</template>

動作に関してはuseForm関数を利用する前と変わりませんがエラーメッセージが”this is a required field”から”name is a required field”に変わっていることが確認できます。

useFormでの動作確認
useFormでの動作確認

yupからすべてをimportしていましたが利用するものだけimportすることでコードが短くなりビルド時のバンドルサイズも小さくすることができます。yup.objectがobject, yup.stringがstringになっています。


<script setup>
import { useField, useForm } from 'vee-validate';
import { object, string } from 'yup';
const schema = object({
  name: string().required(),
  email: string().required().email(),
});
useForm({
  validationSchema: schema,
});
const { value: name, errorMessage: nameError } = useField('name');
const { value: email, errorMessage: emailError } = useField('email');
</script>

useFormのerrors

エラーメッセージについてはuseField関数から戻されるerrorMessageを利用していましたが、useForm関数から戻されるオブジェクトに含まれるerrorsからもメッセージを取得することができます。errorsの中にはnameとemailのエラーメッセージが入ります。


const { errors } = useForm({
  validationSchema: schema,
});

useFormからのエラーを利用するのでuseFieldからのerrorMessageは必要でなくなるため削除することができます。


const { value: name } = useField('name');
const { value: email } = useField('email');

templateタグ側ではerrors.name, errors.emailでエラーメッセージを取得することができます。


<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="name" />
  <p>{{ errors.name }}</p>

  <input type="text" v-model="email" />
  <p>{{ errors.email }}</p>
</template>

動作は先程と変わりません。

useFormでの動作確認
useFormでのerrorsの動作確認

labelのカスタイズ

useFormを利用することでエラーメッセージの中にname, emailが含まれることは先程説明を行いました。メッセージに含まれるnameとemailをカスタマイズすることで好きな名前を表示させることができます。yupを使って行うバリデーションの中でlabel()メソッドを使って変更することができます。nameには名前、emailにはメールアドレスを設定しています。


const schema = object({
  name: string()
    .required()
    .label('名前'),
  email: string()
    .required()
    .email()
    .label('メールアドレス'),
});

動作確認を行うとlabelメソッドの引数に設定した文字列がエラーメッセージの中に表示されます。

labelメソッドによる名前の変更
labelメソッドによる名前の変更

エラーメッセージの設定(日本語)

表示されるメッセージを変更したい場合はrequired, emailメソッドの引数に表示させたいメッセージを設定することでエラーが発生した場合に入力した文字列が表示されます。


const schema = object({
  name: string()
    .required('必須の項目です。'),
  email: string()
    .required('必須の項目です。')
    .email('メールアドレスの形式ではありません。'),
});
エラーメッセージの内容の変更
エラーメッセージの内容の変更

setLocalによる日本語設定

バリデーションのrequired, emailの引数に設定を行うことでエラーメッセージに日本語にすることができましたが、yupが持つsetlocaleメソッドを利用してメッセージをカスタマイズすることができます。setlocaleメソッドを利用して下記のように設定を行うことでエラーが発生した場合に設定した内容が表示されます。


<script setup>
import { useField, useForm } from 'vee-validate';
import { object, string, setLocale } from 'yup';

setLocale({
  mixed: {
    defalut: '不正な値です。',
    required: '必須の項目です。',
  },
  string: {
    email: 'メールアドレスの形式ではありません。',
  },
});

const schema = object({
  name: string()
    .required()
    .label('名前'),
  email: string()
    .required()
    .email()
    .label('メールアドレス'),
});

const { errors } = useForm({
  validationSchema: schema,
});

const { value: name } = useField('name');
const { value: email } = useField('email');
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="name" />
  <p>{{ errors.name }}</p>
  <input type="text" v-model="email" />
  <p>{{ errors.email }}</p>
</template>

上記ではsetLocaleの中でrequired, emailに固定の文字列を設定しましたが関数を利用することで動的にエラーの内容を変更することができます。関数を利用した場合にどのような情報が渡されてくるのか確認を行います。


setLocale({
  mixed: {
    defalut: '不正な値です。!',
    required: (obj) => JSON.stringify(obj),
    // required: '必須の項目です。',
  },
  string: {
    email: 'メールアドレスの形式ではありません。',
  },
});

関数の引数に設定したobjの内容を確認するlabel, pathプロパティにlabelメソッドで設定した値の”名前”が含まれていることが確認できます。

setLocalの関数で渡される値
setLocalの関数で渡される値

もし、labelメソッドが設定されていない場合にはどのような値となるのかも確認しておきます。


const schema = object({
  name: string().required(),
  email: string().required().email(),
});

labelプロパティは表示されなくなり、pathプロパティにはnameが設定されています。

labelメソッドを利用していない場合
labelメソッドを利用していない場合

これらの結果からバリデーションのエラーメッセージにフィールド名を表示させたい場合にpathまたはlabelが利用できることがわかります。下記ではlabelを利用して設定を行います。


setLocale({
  mixed: {
    defalut: '不正な値です。!',
    required: ({ label }) => `${label}は必須の項目です。`,
    // required: '必須の項目です。',
  },
  string: {
    email: ({ label }) => `${label}の形式ではありません。`,
  },
});

ブラウザで確認するとlabelで指定した”名前”がバリデーションのメッセージに含まれていることがわかります。yupが持つsetLocale関数を利用してエラーメッセージを日本語化することができました。

labelの値をエラーメッセージの中に表示
labelの値をエラーメッセージの中に表示

アプリケーションの起動時に必ず読み込まれるルートファイルmain.jsファイルの中でsetLocaleを設定することでApp.vueでもロケール設定が有効になるためApp.vueファイルを含め他のコンポーネントファイルでもmain.jsファイルで設定されたメッセージが表示されます。setLocaleの引数に設定するファイルを作成しておくことでyupを利用するどのプロジェクトでも再利用することができます。


import { createApp } from 'vue';
import App from './App.vue';
import { setLocale } from 'yup';

setLocale({
  mixed: {
    defalut: '不正な値です。!',
    required: ({ path }) => `${path}は必須の項目です。`,
  },
  string: {
    email: ({ label }) => `${label}の形式ではありません。`,
  },
});

イベントの設定 handleChange

文字を入力するとバリデーションが行われていましたがuseFieldから戻されるオブジェクトに含まれるhandleChange関数を利用してバリデーションのタイミングを変更することができます。

emailのみ利用して動作確認を行うためhandleChagenメソッドをuseFieldの戻り値から取得します。


const { value: email, handleChange } = useField('email');

@inputイベントを利用して設定を行います。inputイベントでは文字を入力する度にイベントが発生するためこれまでと動作は変わりません。v-model=”email”から:value=”email”に変更も必要です。v-modelを設定したままだと文字を入力毎にバリデーションが行われます。


<input type="text" @input="handleChange" :value="email" />

@inputイベントから@changeイベントに変更します。


<input type="text" @change="handleChange" :value="email" />

文字を入力している時はバリデーションは行われませんが”Enter”ボタンをクリックするとイベントが発生してバリデーションが行われます。入力フィールドからカーソルを外しても発生します。

@changeイベントから@blurイベントに変更します。@blurイベントを設定した場合は入力フィールドからカーソルを外した場合にバリデーションが行われます。文字に何も入力していなくても一度フィールドにカーソルを合わせて、カーソルを外すとバリデーションが行われます。


<input type="text" @blur="handleChange" :value="email" />

フォーカスが外れた時にイベントが発火されるため何もinput要素に入力していなくてもフォーカスが外れるとバリデーションが行われます。

このようにhandleChange関数を利用してバリデーションを行うタイミングを制御することができます。

useFieldで初期値の設定

ここまではinput要素に初期値は設定されていませんでしたが、useField関数を使ってinput要素に初期値を設定することができます。初期値を設定する場合はuseFieldの第3引数を利用します。第2引数はバリデーションの関数を設定するために利用することができますがuseForm関数を利用した場合はスキーマでバリデーションを設定しているので設定する必要がないためundefinedとします。下記のようにinitialValueを使って初期値を設定します。


const { value: name, errorMessage: nameError } = useField(
  'name',
  undefined,
  { initialValue: 'John' }
);

ブラウザでアクセスすると初期値が入った状態で表示されます。

useFieldでの初期値の設定
useFieldでの初期値の設定

useFormで初期値を設定

useField関数で初期値を設定することができましたがuseFieldで初期値を設定する場合はuseField毎に設定を行う必要があります。useFormを利用することで一箇所で初期値を設定することができます。initialValueではなくinitialValuesと”s”がついているので注意してください。


const schema = object({
  name: string().required(),
  email: string().required().email(),
});
const formValues = {
  name: 'John',
  email: 'john@example.com',
};
const { errors } = useForm({
  validationSchema: schema,
  initialValues: formValues,
});

ブラウザで確認すると設定した初期値がinput要素に入力された状態で表示されます。

useFormでの初期値の設定
useFormでの初期値の設定

Validation Metadata

メタデータについては公式ドキュメントのhttps://vee-validate.logaretm.com/v4/guide/composition-api/validation/#validation-metadataに記述されています。

メタデータにはどのようなものがあるのかまたメタデータはどのような時に使えるのかを確認していきます。

useFieldのメタデータ

useFiledから戻されるオブジェクトの中にはメタデータが含まれています。

metaオブジェクトにはvalid, touched, dirty, pending, initialValueが含まれています。ブラウザ上に各値を表示させ入力によってどのような変化があるのか確認していきます。

inputへの入力によって変化のあるvalid, dirtyとinitialValueを表示させます。後ほどtouchedについては動作確認を行っています。


<script setup>
import { useField, useForm } from 'vee-validate';
import { object, string } from 'yup';
const schema = object({
  name: string().required(),
  email: string().required().email(),
});

const { errors } = useForm({
  validationSchema: schema,
});

const { value: name, handleChange } = useField('name');
const { value: email, meta } = useField('email');
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="name" />
  <p>{{ errors.name }}</p>
  <input type="text" v-model="email" />
  <p>{{ errors.email }}</p>
  <p>valid:{{ meta.valid }}</p>
  <p>dirty:{{ meta.dirty }}</p>
  <p>initialValue:{{ meta.initialValue }}</p>
</template>

ブラウザで確認するとvalidとdirtyの値はfalseでinitialValueには何も入っていません。

メタデータの初期値の確認
メタデータの初期値の確認

input要素に文字を入力します。1文字入れた瞬間にバリデーションのエラーが表示されますがそれと一緒にdirtyの値がfalseからtrueになることが確認できます。

dirty値の値の変化
dirty値の値の変化

dirtyの説明についてはドキュメントでは” If the field value was updated, you cannot change its value.”と記載されています。ドキュメントの説明を読んだだけでは理解することが難しいですがinput要素に文字を入れて値を更新するとfalseからtrueになります。一度dirtyがtrueになったらfalseに戻すことはできないため、ドキュメントでは”you cannot change its value”と説明しているのでしょう。dirty値を利用することでinput要素に入力があったということを判定することができるようになります。

さらに文字の入力を続けて正しいメールアドレスを入力するとvalidはtrueになります。

validの値がtrueに
validの値がtrueに

ドキュメントでのvalidの説明は、”The current field validity, automatically updated for you.”と記載されています。動作確認からバリデーションにパスしたら値がtrueになることがわかりました。validの値によってそのフォールドにおけるバリデーションがパスしたかどうか確認することができますた。dirtyの値は一度更新されると元の値に戻ることはありませんが入力した文字列がメールアドレスの形式と異なる形に戻すと一度trueになっても再度falseになります。

initialValueの値は何も入っていないので設定を行います。


const { value: email, meta } = useField('email', undefined, {
  initialValue: 'john@test.com',
});

ブラウザで確認するとinitialValueには設定した値が入り、メールアドレスの正しい形式なのでvalidの値もtrueになっていることが確認できます。input要素の文字を変更してもinitialValueの値が変わることはありません。

initialValueを設定した場合
initialValueを設定した場合

meta.dirtyとinitial Valueの注意点としてドキュメントには下記のように記述されています。


Notice in the previous example, we passed an initialValue, this is because the default field value is undefined which may cause unexpected meta.dirty results.

To get accurate results for the meta.dirty flag, you must provide an initial value to your field even if the values are empty.

initialValueを設定していない場合にmeta.dirtyが期待した通り値にならない場合があるので初期値がない場合でもinitialValueの設定する必要があると記載されているので忘れずにinitialValueの設定を行う必要があります。

Metadataの利用場所

dirtyやvalidの値がどのように変化するのかを理解することができました。ではdirty, validはいつ利用することができるのでしょう。

入力フォームの場合、入力後に送信ボタンを押します。バリデーションをパスしていない場合に送信ボタンを押せないようにメタデータを制御することができます。button要素のdisabled属性を利用してmeta.validがfalseの場合はボタンをクリックできないように設定を行っています。


<button :disabled="!meta.valid">送信</button>
ボタンがクリックできない
ボタンがクリックできない

バリデーションにパスするとmeta.validの値がtrueになるので送信ボタンがクリックできるようになります。

送信ボタンがクリックできる
送信ボタンがクリックできる

meta.validを利用した一覧ですがメタデータの値を組み合わせることでフォームの制御を行うことができます。

handleBlurでメタデータを更新

イベントとhandleChangeを利用することでバリデーションを実行するタイミングを変更することができました。イベントに関連する関数handleChangeの他にhandleBlurがあります。handleBlurはバリデーションではメタデータの一つの値であるtouchedの値を更新するために利用することができます。handleChangeのようにバリデーションの実行は行いません。

useField関数から戻されるオブジェクトにhandleBlur関数は含まれています。


const { value: email, meta, handleBlur } = useField('email');

@inputイベントに設定することで文字を入力するとすぐにイベントが発火されhandleBlurによりmeta.touchedの値がfalseからtrueに変わります。blurという名前がついていますが@input, @blur, @changeイベントで設定できる関数です。


<input type="text" v-model="email" @input="handleBlur" />

@inputイベントから@blurイベントに変更することでフォーカスが外れた時にmeta.touchedの値を更新するといったことが可能になります。何も入力を行わなくても一度フォーカスしてフォーカスを外すとfalseからtrueに変わります。meta.tochedの値を使って入力フォームを制御したい場合にhandleBlurを利用することができます。


<input type="text" v-model="email" @blur="handleBlur" />

useFormのメタデータ

useForm関数から戻されるオブジェクトにもmetaが含まれています。useFiledとは異なり複数の要素に関するメタデータを持っているのでinput要素への入力によってどのようにメタデータの値が変わるか確認します。

useFieldでは初期値はmeta.initialValueにはいっていましたが、useFormではmeta.initinalValuesとなります。


<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="name" />
  <p>{{ errors.name }}</p>
  <input type="text" v-model="email" />
  <p>{{ errors.email }}</p>
  <p>valid:{{ meta.valid }}</p>
  <p>dirty:{{ meta.dirty }}</p>
  <p>initialValues:{{ meta.initialValues }}</p>
</template>

<script>
import { useField, useForm } from 'vee-validate';
import { object, string } from 'yup';
export default {
  setup() {
    const schema = object({
      name: string().required(),
      email: string()
        .required()
        .email(),
    });

    const { errors, meta } = useForm({
      validationSchema: schema,
      initialValues: {
        name: '',
        email: '',
      },
    });

    const { value: name } = useField('name');
    const { value: email } = useField('email');

    return {
      name,
      email,
      errors,
      meta,
    };
  },
};
</script>

ブラウザで確認するとinitialValuesはオブジェクトになっており、nameとemailの初期値は””なので””となっています。

useFormのメタデータ
useFormのメタデータ

どちらかのinput要素に文字を入力するとdirtyがtrueに変わります。dirtyは両方のinput要素に入力したらtrueになるわけではなくいずれかのinput要素に入力を行うとtrueになります。nameについてはrequiredのバリデーションしか設定していないので文字を入力しただけでバリデーションはパスしますがvalidの値はfalseのままです。

dirtyとvalidの値を確認
dirtyとvalidの値を確認

正しい形式のメールアドレスを入力するとvalidの値はtrueになります。複数のinput要素に対してバリデーションが設定されている場合useFormではどちらのバリデーションもパスしないとvalidの値はtrueにならないことがわかりました。

validの値がtrueに

handleReset関数

useFieldから戻されるオブジェクトの中にhandleChange, handleBlurがあることを確認しましたがもう一つhandleResetという関数があります。handleResetを利用するとフィールドに入力した値をクリアしてくてれます。

useFieldModel関数

v4.6からscript setupの環境でのみuseFieldModelを利用することができます。useFieldModleはuseField関数を置き換えることができます。


const { errors, useFieldModel } = useForm({
  validationSchema: schema,
  initialValues: {
    name: '',
    email: '',
  },
});

const name = useFieldModel('name');
const email = useFieldModel('email');
</script>

さらに配列を利用して一括で設定を行うことができます。


<script setup>
import { useForm } from 'vee-validate';
import { object, string } from 'yup';
const schema = object({
  name: string().required(),
  email: string().required().email(),
});

const { errors, useFieldModel } = useForm({
  validationSchema: schema,
  initialValues: {
    name: '',
    email: '',
  },
});

const [name, email] = useFieldModel(['name', 'email']);
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="name" />
  <p>{{ errors.name }}</p>
  <input type="text" v-model="email" />
  <p>{{ errors.email }}</p>
</template>

フォームの処理

通常フロントエンドの入力フォームで入力されたデータはバックエンドサーバに送信されサーバ側では送られたデータを使って決められた処理(データベースへの登録)を実行します。ログインフォームの場合は入力したメールアドレスとパスワードをサーバに送信しログイン処理を行います。ここまではuseField, useFormを利用してバリデーション方法を確認してきましたがここでは入力したデータをどのように扱うことができるのか確認していきます。

入力したデータを取得

最初に入力した値をVueのコード内で取得する方法を確認します。

formタグを追加しsubmitイベントを設定します。送信ボタンをクリックするとonSubmit関数が実行できるように設定を行います。


<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <form @submit="onSubmit">
    <input type="text" v-model="name" />
    <p>{{ errors.name }}</p>
    <input type="text" v-model="email" />
    <p>{{ errors.email }}</p>
    <button type="submit">送信</button>
  </form>
</template>

設定したonSubmit関数を追加し入力したデータを取得するコードを記述していきます。useForm関数から戻されるオブジェクトに含まれるhandleSubmit関数を利用することでデータの取得を行うことができます。下記ではhandleSubmit関数の引数にcallback関数を入れて渡されるvaluesの値をconsole.logで表示しています。


import { useField, useForm } from 'vee-validate';
import { object, string } from 'yup';
const schema = object({
  name: string().required(),
  email: string().required().email(),
});

const { errors,handleSubmit } = useForm({
  validationSchema: schema,
  initialValues: {
    name: '',
    email: '',
  },
});

const onSubmit = handleSubmit((values) => {
  console.log(values);
});

const { value: name } = useField('name');
const { value: email } = useField('email');

設定後に入力フォームに入力を行い送信ボタンを押します。バリデーションにパスできる値を入力しています。

入力を行い送信ボタンをクリック
入力を行い送信ボタンをクリック

ブラウザのデベロッパーツールのコンソールを確認します。入力した値が表示されます。

フォームに入力した値が表示
フォームに入力した値が表示

次にバリデーションをパスしていない状態で送信ボタンをクリックしてください。クリックしてもバリデーションにパスしていないため処理が行われずコンソールに入力したデータが表示されることはありません。handleSubmitのcallback関数はvalidの時のみ実行されます。

submitイベントにprevent(preventDefault)を設定していない場合はページのリロードが行われますがhandleSubmit関数を利用するとpreventの設定を自動で行ってくれるため設定をする必要がありません。vee-validateを利用しない環境では通常@submit.preventと設定します。

handleSubmitの進捗の確認

handleSubmit関数の中では入力したデータをバックエンドサーバに送信する等の処理を行います。処理中に何度も送信ボタンを押させないために処理の進捗を確認することで制御することができます。useFormから戻されるオブジェクトに含まれるisSubmittingを利用します。isSubmittingはデフォルトではfalseでhandleSubmit関数が処理中はtrueになり、処理が完了するとfalseになります。この値をbuttonのdisabled属性で利用します。


<button type="submit" :disabled="isSubmitting">送信</button>

通常はフォームで入力した値を送信する処理を行いますがPromiseを利用した遅延処理を追加したisSubmittingの動作確認を行います。


const { errors, handleSubmit, isSubmitting } = useForm({
  validationSchema: schema,
  initialValues: {
    name: '',
    email: '',
  },
});

const onSubmit = handleSubmit(async (values) => {
  await new Promise((resolve) => setTimeout(resolve, 5000));
  console.log(values);
});

設定後、フォームに入力して送信ボタンを押すとPromiseで設定したsetTimeoutによる5秒間の待機中は送信ボタンはクリックできない状態になります。

データ取得中はボタンが無効になる
データ取得中はボタンが無効になる

このようにisSubmittingを利用することで処理中に”送信”ボタンを誤って複数回クリックするという問題を回避することができます。

submitカウントの取得

ログインフォームを利用してログインする場合にログインの失敗する回数を保存しておき事前に決めた回数を超えるとログインができなくなるといった実装を見かけることがあります。Vee-Validateではsubmitの回数を保存することができるのでその回数を利用してフォームの制御を行うことができます。useFormから戻されるオブジェクトの中にsubmitCountが含まれているのでそれを利用します。

submitCountとComputedプロパティを使って5回送信ボタンを押すとフォームをv-ifディレクティブで非表示になるようにしています。


<script setup>
import { computed } from 'vue';
import { useField, useForm } from 'vee-validate';
import { object, string } from 'yup';
const schema = object({
  name: string().required(),
  email: string().required().email(),
});

const { errors, handleSubmit, isSubmitting, submitCount } = useForm({
  validationSchema: schema,
  initialValues: {
    name: '',
    email: '',
  },
});

const onSubmit = handleSubmit(async (values) => {
  await new Promise((resolve) => setTimeout(resolve, 5000));
  console.log(values);
});

const isTooManyAttempts = computed(() => {
  return submitCount.value >= 5;
});

const { value: name } = useField('name');
const { value: email } = useField('email');
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <form @submit="onSubmit" v-if="!isTooManyAttempts">
    <input type="text" v-model="name" />
    <p>{{ errors.name }}</p>
    <input type="text" v-model="email" />
    <p>{{ errors.email }}</p>
    <button type="submit" :disabled="isSubmitting">送信</button>
  </form>
</template>

バリデーションにパスしているかどうかに関わらず5回送信ボタンを押すとフォームが非表示になります。

入力した値をリセット

handleSubmitで入力した値をサーバに送信後フォームの値をリセットしたい場合があります。その場合のresetForm関数も準備されています。バリデーションにパスする値を入力して送信ボタンをクリックするとフォームに入力した値がリセットされます。


const onSubmit = handleSubmit((values, { resetForm }) => {
  console.log(values);
  //入力した値をサーバに送信したりする処理
  resetForm();
});

ネストされたオブジェクトデータ

ネストされたオブジェクトデータを扱いたい時にはuseFieldで.(ドット)を使って下記のように記述することで利用することができます。これだけで設定が終わりではなくバリデーションスキーマ、初期値の設定、エラーの設定などの変更を行う必要があります。


const { value: name } = useField('form.name');
const { value: email } = useField('form.email');

初期値については下記のように変更を行います。


const { errors, meta, handleSubmit, isSubmitting } = useForm({
  validationSchema: schema,
  initialValues: {
    form: {
      name: '',
      email: '',
    },
  },
});

バリデーションスキーマについては下記のように変更を行います。


const schema = object({
  form: object({
    name: string().required('必須の項目です。'),
    email: string()
      .required('必須の項目です。')
      .email('メールアドレスの形式ではありません。'),
  }),
});

templateタグ内のエラーについては下記のように変更を行います。


<input type="text" v-model="name" />
<p>{{ errors['form.name'] }}</p>
<input type="text" v-model="email" />
<p>{{ errors['form.email'] }}</p>

設定完了後にinput要素に入力を行いエラーメッセージが正しく表示されることを確認してバリデーションをパスするデータを入力して送信ボタンを押してください。

表示されるデータがネストされたデータになっていることを確認してください。

ネストされたオブジェクトが表示
ネストされたオブジェクトが表示

ネストされたオブジェクトも扱えることが確認できました。

Componentsの場合

ここまではComposition APを利用したい場合のvee-validateの説明を行ってきましたが、Componentsについても動作確認を行っていきます。Componentsは名前の通りコンポーネントを利用して行います。利用するコンポーネントの名前はForm, Field, ErrorMessageコンポーネントです。

Formコンポーネントがformタグ、Fieldコンポーネントがinputタグ、ErrorMessageはデフォルトではspanタグの中にバリデーションのエラーが表示されるコンポーネントです。

Form, Field, ErrorMessageを使った最もシンプルなフォームが下記です。バリデーションはFieldのrulesに設定することができます。


<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <Form>
    <Field name="name" :rules="isRequired" />
    <ErrorMessage name="name" />
  </Form>
</template>

<script>
import { Field, Form, ErrorMessage } from 'vee-validate';
export default {
  components: { Field, Form, ErrorMessage },
  setup() {
    const isRequired = (value) => {
      if (value && value.trim()) {
        return true;
      }

      return 'This is required';
    };
    return {
      isRequired,
    };
  },
};
</script>

ブラウザで確認するとinput要素が表示されます。

Componentsを利用した場合
Componentsを利用した場合

input要素に文字を入力して削除をしてその後フォーカスを外してください。フォーカスを外すとバリデーションが行われます。フォーカスを外すまではバリデーションは行われません。

@inputイベントではなく@changeイベントが設定されているためフォーカスが外れた後にバリデーションが実行されます。

デベロッパーツールを使ってHTMLの要素を確認します。各コンポーネントがどのHTML要素に対応するかがはっきりとわかります。

html要素の確認
html要素の確認

バリデーションスキーマ

先程はisRequired関数でバリデーションを行っていきましたが複数のinput要素、yup, バリデーションスキーマを利用してコードの書き換えを行います。ErrorMessageコンポーネントではデフォルトではspanタグなのでas=”div”によりspanタグからdivタグに変更することができます。


<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <Form :validation-schema="schema">
    <div>
      <Field name="name" />
    </div>
    <ErrorMessage name="name" as="div" />
    <div>
      <Field name="email" />
    </div>
    <ErrorMessage name="email" as="div" />
  </Form>
</template>

<script>
import { Field, Form, ErrorMessage } from 'vee-validate';
import * as yup from 'yup';
export default {
  components: { Field, Form, ErrorMessage },
  setup() {
    const schema = yup.object({
      name: yup.string().required(),
      email: yup
        .string()
        .required()
        .email(),
    });

    return {
      schema,
    };
  },
};
</script>

バリデーションも正しく行われます。

バリデーションエラーの表示
バリデーションエラーの表示

submit処理

submit処理を行いたい場合は、Formタグに@submitイベントを設定してonSubmit関数を設定します。


<Form @submit="onSubmit" :validation-schema="schema">

onSubmit関数の中で入力したデータを取得します。


const onSubmit = (values) => {
    //サーバへのデータ送信などの処理
    console.log(values);
  };

  return {
    schema,
    onSubmit,
  };
},

バリデーションにパスするデータを入力して送信ボタンを押します。

送信ボタンをクリック
送信ボタンをクリック

フォームに入力した値を取得することができます。

入力したデータを取得
入力したデータを取得

Componentsを利用してもComposition APIと同様に処理が行えることがわかります。

まとめ

本書を通してVue3のComposition APIでのvee-validateのフォームのデータ管理、バリデーション処理、エラーメッセージの表示、フォームのsubmit処理の基本設定を学ぶことができました。vee-validateの公式ドキュメントのComposition APIに関するページについてはほとんど網羅しているのでこの後ドキュメントを読むと理解もより深まると思います。本文書を参考により複雑なフォームの作成にvee-validateを利用してみてください。