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

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

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

Vue3環境の構築

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


 % npm create 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-validate
✔ 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-validate...

Done. Now run:

  cd vite-vee-validate
  npm install
  npm run dev

vee-validateのインストール

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


% npm install vee-validate --save

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


{
  "name": "vite-vee-validate",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "vee-validate": "^4.13.2",
    "vue": "^3.4.29"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.0.5",
    "vite": "^5.3.1"
  }
}

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

Vue.jsで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-validate@0.0.0 dev
> vite


  VITE v5.3.4  ready in 350 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

ブラウザでアクセスすると下記の画面が表示されます。

初期設定後のページの確認
初期設定後のページの確認

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要素に入力した文字をブラウザ上に表示させることができました。

Vue.jsでフォームの理解にはこちらの記事がおすすめです。

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

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に書き換えてもinput要素に文字を入力するとinput要素の下に入力した文字列が表示されます。

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

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, valibotを利用することができます。

@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からimportしたrequiredではどのような処理が行われているのか気になる人はnode_modules/rules/dist/vee-validate-rules.jsファイルで確認することができます。requiredValidator関数として以下の処理が記述されています。他のルールも見てぜひ参考にしてJavaScriptのコーディングのスキルアップに繋げてください。


//略
function isEmptyArray(arr) {
    return Array.isArray(arr) && arr.length === 0;
}
const isObject = (obj) => obj !== null && !!obj && typeof obj === 'object' && !Array.isArray(obj);

const requiredValidator = (value) => {
    if (isNullOrUndefined(value) || isEmptyArray(value) || value === false) {
        return false;
    }
    return !!String(value).trim().length;
};
//略

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

バリデーションルール一覧
バリデーションルール一覧
ドキュメントにはLaravel Framworkのvalidataionのsyntaxにinspireされていると明記されている通り、Laravel経験がある人には見慣れた名前になっています。
fukidashi

他のルールを設定してもエラーメッセージはすべて”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>

vee-validate/i18nを設定する前まではメッセージは””name is not valid””でしたが、設定後はメッセージが”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 { all } from '@vee-validate/rules';

Object.entries(all).forEach(([name, rule]) => {
  defineRule(name, rule);
});

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

localize('ja');

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

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

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


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

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

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

複数のinput要素

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


<script setup>
import { useField } from 'vee-validate';
const { value: name, errorMessage: nameError } = useField('name', 'required');
const { value: email, errorMessage: emailError } = useField('email', '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要素にメールアドレスを入れるため文字列を入力します。入力した文字列がメールアドレスの形式になっていないためエラーが表示されます。

二つ目のinput要素のエラーメッセージ
二つ目のinput要素のエラーメッセージ

一つ目のinput要素に文字を入力して削除するとエラーメッセージが表示されます。

2つの要素でのエラーメッセージの表示
2つの要素でのエラーメッセージの表示

john@test.comなどメール形式の文字列を入力するとエラーが表示されなくなります。これまでバリデーションのrequireのみ確認してきましたがemailのバリデーションも問題なく動作することが確認できました。

イベントの設定 handleChange

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

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


const {
  value: email,
  errorMessage: emailError,
  handleChange,
} = useField('email', '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" />

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

useFieldで初期値の設定

ここまではinput要素に初期値は設定されていませんでしたが、useField関数を使ってinput要素に初期値を設定することができます。初期値を設定する場合はuseFieldの第3引数を利用します。第3引数ではinitialValueのほかにlabelやtypeも設定できます。


const { value: email, errorMessage: emailError } = useField('email', 'email', {
  initialValue: 'John',
});

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

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

labelに”メールアドレス”を設定した場合はエラーメッセージがemailからメールアドレスに変わります。


const { value: email, errorMessage: emailError } = useField('email', 'email', {
  initialValue: 'John',
  label: 'メールアドレス',
});
labelの設定
labelの設定

その他のuseFiledで利用できる第3引数のオプションはhttps://vee-validate.logaretm.com/v4/api/use-field#additional-optionsで確認できます。

useFieldのメタデータ

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

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

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


<script setup>
import { useField } from 'vee-validate';
const { value: name, errorMessage: nameError } = useField('name', 'required');
const {
  value: email,
  errorMessage: emailError,
  meta,
} = useField('email', 'email', {
  initialValue: 'John',
  label: 'メールアドレス',
});
</script>

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

ブラウザで確認するとvalidとdirtyの値はfalseでinitialValueにはuseFiledで設定した値が含まれています。

useFieldから戻されるmeta情報の表示
useFieldから戻されるmeta情報の表示

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

文字入力後のmeta情報の変化
文字入力後のmeta情報の変化

dirtyの説明についてはドキュメントでは” if the field has been manipulated”と記載されており、input要素が更新されたかどうかがこの値によってわかります。つまりdirty値を利用することでinput要素に入力があったということを判定することができるようになります。

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

バリデーションにパスするとvalidはtrueに
バリデーションにパスするとvalidはtrueに

ドキュメントにはvalidの説明に”if the field doesn’t have any errors”と記述さえれているのでバリデーションにエラーがない場合はtrueになります。

再度input要素の文字列をメールアドレスの形式ではない形に更新してください。validの値がtrueからfalseになることが確認できます。

Metadataの利用場所

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

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


<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="name" />
  <p>{{ nameError }}</p>
  <input type="text" v-model="email" />
  <p>{{ emailError }}</p>
  <button :disabled="!meta.valid">送信</button>
</template>
ボタンがクリックできない
ボタンがクリックできない

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

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

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

useFiledでは個別の要素に対してメタデータを取得することができますがフォーム全体のメタデータは後ほど出てくるuseFormから戻されるメタデータを利用します。

handleBlurでメタデータを更新

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

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


const {
  value: email,
  errorMessage: emailError,
  meta,
  handleBlur,
} = useField('email', 'email', {
  initialValue: 'John',
  label: 'メールアドレス',
});

@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" />

handleReset関数

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

バリデーションライブラリの利用

バリデーションに@vee-validate/rulesを利用してきましたが、ここからは外部のバリデーションライブラリを利用して動作確認を行なっていきます。ドキュメントの中でyupを利用して説明していたこともあり、本文書ではyupを利用して動作確認を行っていますが、zod, valibotなどもサポートされているので使い慣れているバリデーションライブラリを利用することができます。

yupのインストール

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


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

インストールが完了したらApp.vueファイルでyupのimportを行い、先程設定していたバリデーション関数をyupに変更します。必須項目なのでyup.required()ではと思ってしまうかもしれませんが文字列を扱っているのでyup.string().required()のようにstring()が必要です。string()が抜けると”TypeError: yup.required is not a function”のエラーがコンソールに表示されます。


<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>

useFormの利用

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

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


import * as yup from 'yup';

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

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


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

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


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

useForm関数を利用すると最終的には下記のようなコードに書き換えることができます。


<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のvalues

useFormは戻り値のオブジェクトを持っており、さまざまなプロパティが含まれている。例えばvaluesの中にはinput要素で入力した値が含まれています。


<script setup>
//略
const { values } = useForm({
  validationSchema: schema,
});
//略
</script>

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

ページを開いた直後はvaluesの値は空のオブジェクトですが、input要素に入力すると入力した値が表示されます。

useFormから戻されるvaluesの確認
useFormから戻されるvaluesの確認

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の動作確認

useFormのdefineField

useFormからはdefineField関数も戻されます。例えばschemaで設定したnameをdefineFiledの引数に設定することでinput要素のv-modelのデータが戻され、defineFiled関数を利用した場合はuseField関数を利用する必要はありません。


<script setup>
import { useForm } from 'vee-validate';
import * as yup from 'yup';

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

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

const [name, nameProps] = defineField('name');
const [email, emailProps] = defineField('email');
</script>

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

defineFiledではその他にどのような設定があるかドキュメントで確認することができます。


const [field, props] = defineField('field', {
  // A getter that computes and adds any additional props to the component
  // It receives the current field state as an argument
  props(state) {
    // This is just an example, by default this is an empty object
    return {
      'aria-invalid': state.errors.length > 0 ? 'true' : 'false',
    };
  },
  // A label for the field, only used with global validation rules
  label: 'a label',
  // Validates when `blur` event is emitted from the element/component
  validateOnBlur: true,
  // Validates when `change` event is emitted from the element/component
  validateOnChange: true,
  // Validates when `input` event is emitted from the element/component
  validateOnInput: false,
  // Validates when the returned model value changes
  validateOnModelUpdate: true,
});

emailのバリデーションを設定したinput要素に文字を入力すると入力する度にバリデーションが実行されます。defineFileldの設定でバリデーションを行うタイミングを変更することができます。

validateOnModelUpdateの値をデフォルトのtrueからfalseに変更します。


const [name, nameProps] = defineField('name');
const [email, emailProps] = defineField('email', {
  validateOnModelUpdate: false,
});

設定を行うと文字を入力してもエラーメッセージが表示されなくなります。Enterまたはカーソルを外すとバリデーションが実行されます。

input要素のv-bindでemailPropsを指定していましたがvalidateOnModelUpdate: falseのままemailPropsをinput要素から削除します。入力後、カーソルを外してもEnterボタンをクリックしてもバリデーションは行われなくなります。emailPropsの中でイベントに関する情報が含まれていることがわかります。defineField(‘email’)の場合はemailPropsをinput要素に設定していなくても入力するとすぐにバリデーションが実行されます。

defineFiledのvalidateOnBlurやvalidateOnChangeなどを利用してバリデーションのタイミングを変更することができます。

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 { 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 { values, errors, defineField } = useForm({
  validationSchema: schema,
});

const [name, nameProps] = defineField('name');
const [email, emailProps] = defineField('email');
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <input type="text" v-model="name" v-bind="nameProps" />
  <p>{{ errors.name }}</p>
  <input type="text" v-model="email" v-bind="emailProps" />
  <p>{{ errors.email }}</p>
  <pre>{{ values }}</pre>
</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}の形式ではありません。`,
  },
});

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 { values, errors, defineField } = useForm({
  validationSchema: schema,
  initialValues: formValues,
});

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

useFormのメタデータ

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

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


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

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

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

const [name, nameProps] = defineField('name');
const [email, emailProps] = defineField('email');
</script>

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

ブラウザで確認すると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に

Zodを利用した場合

バリデーションライブラリをyupの代わりにzodを利用した場合の設定について簡単に説明しておきます。zodを利用するためにzodと@vee-validate/zodのインストールを行います。


% npm install zod @vee-validate/zod

schemaの定義方法が少し異なりますが残りのvee-validateの設定箇所については違いはありません。


<script setup>
import { useForm } from 'vee-validate';
import { toTypedSchema } from '@vee-validate/zod';
import * as z from 'zod';

const schema = toTypedSchema(
  z.object({
    name: z.string().min(1, { message: 'Field is required' }),
    email: z
      .string()
      .min(1, { message: 'Field is required' })
      .email({ message: 'Must be a valid email' }),
  })
);

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

const [name, nameProps] = defineField('name');
const [email, emailProps] = defineField('email');
</script>

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

input要素以外の設定

input要素以外の要素を利用した場合の設定について確認します。

checkboxの場合

input要素のtype属性をcheckboxに設定して表示されるcheckboxをチェックするかどうかで値がtrueかfalseとなります。必ずチェックが必要な場合のバリデーションを設定しています。


<script setup>
import { useForm } from 'vee-validate';
import * as yup from 'yup';

const schema = yup.object({
  checkValue: yup.bool().oneOf([true], 'Field must be checked'),
});

const { errors, defineField, handleSubmit } = useForm({
  validationSchema: schema,
  initialValues: {
    checkValue: false,
  },
});

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

const [checkValue, checkValueProps] = defineField('checkValue');
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <form @submit="onSubmit">
    <input type="checkbox" v-model="checkValue" v-bind="checkValueProps" />
    <p>{{ errors['checkValue'] }}</p>
    <button type="submit">送信</button>
  </form>
</template>

チェックをしていない状態で送信ボタンをクリックするとバリデーションでエラーとなり、エラーメッセージが表示されます。

checkboxでの設定確認
checkboxでの設定確認

チェックを行っている場合にはコンソールにtrueが表示されます。

複数のcheckboxを持つ場合

1つのチェックボックスではデフォルトでは真偽値のtrue, falseの2つをとることができますが、複数のチェックボックスの場合は各チェックボックスで設定したvalueの値を配列で取得します。

最低一つチェックボックスを選択しているかどうかバリデーションを利用してチェックしています。


<script setup>
import { useForm } from 'vee-validate';
import * as yup from 'yup';

const schema = yup.object({
  checkValue: yup.array(yup.string().required()).min(1),
});

const { errors, defineField, handleSubmit } = useForm({
  validationSchema: schema,
  initialValues: {
    checkValue: [],
  },
});

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

const [checkValue, checkValueProps] = defineField('checkValue');
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <form @submit="onSubmit">
    <input type="checkbox" v-model="checkValue" value="ラグビー" /> ラグビー
    <input type="checkbox" v-model="checkValue" value="サッカー" /> サッカー
    <input type="checkbox" v-model="checkValue" value="バスケットボール" />
    バスケットボール
    <p>{{ errors['checkValue'] }}</p>
    <button type="submit">送信</button>
  </form>
</template>

チェックボックスを一つも選択していない場合にはエラーメッセージが表示され、一つでも選択して送信ボタンをクリックするとコンソールには配列で選択した名前が配列に入って表示されます。

複数のチェックボックスを設定した場合
複数のチェックボックスを設定した場合

フォームの処理

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

入力したデータを取得

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

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


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

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


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

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

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

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

const [name, nameProps] = defineField('name');
const [email, emailProps] = defineField('email');
</script>

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

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

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

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

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

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

handleSubmitの進捗の確認

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


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

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


const { errors, defineField, 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 { useForm } from 'vee-validate';
import { computed } from 'vue';
import { object, string } from 'yup';

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

const { errors, defineField, 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 [name, nameProps] = defineField('name');
const [email, emailProps] = defineField('email');
</script>

<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <form @submit="onSubmit" v-if="!isTooManyAttempts">
    <input type="text" v-model="name" v-bind="nameProps" />
    <p>{{ errors.name }}</p>
    <input type="text" v-model="email" v-bind="emailProps" />
    <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 schema = object({
  form: object({
    name: string().required('必須の項目です。'),
    email: string()
      .required('必須の項目です。')
      .email('メールアドレスの形式ではありません。'),
  }),
});

これだけで設定が終わりではなく初期値の設定、エラーの設定などの変更を行う必要があります。

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


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

defineFieldの引数も変更します。


const [name, nameProps] = defineField('form.name');
const [email, emailProps] = defineField('form.email');

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


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

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

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

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

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

Componentsの場合

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

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

Form, Field, ErrorMessageを使った最もシンプルなフォームが下記です。バリデーションはFieldのrulesに設定することができます。Options APIとComposition APIを利用した場合のコードを記述しています。どちらも動作は同じです。


<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>

<script setup>
import { Field, Form, ErrorMessage } from 'vee-validate';

const isRequired = (value) => {
  if (value && value.trim()) {
    return true;
  }

  return 'This is required';
};
</script>
<template>
  <h1>Vee-Validateでフォームバリデーション</h1>
  <Form>
    <Field name="name" :rules="isRequired" />
    <ErrorMessage name="name" />
  </Form>
</template>

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

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

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

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

デベロッパーツールを使って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を利用してみてください。