本文書では開発したアプリケーションを世に出す前には避けて通れないフロントエンド側のバリデーションについて解説しています。エラー処理の設定には手間がかかってあまり好きではないなという人も多いとは思います。Vuelidateを利用するとバリデーションの設定がなくなるわけではありませんがばバリデーション処理の設定が楽になることは間違いないのでぜひ興味がある人はVuelidate試してみてください。

Vue.jsにはさまざまなバリデーションのライブラリがありますが、その中でもVuelidateとVeeValidateが有名です。本文書ではVue3のComposition APIを利用してVuelidateの動作確認を行っています。Vuelidateのバージョン2ではVue.js 3と2をサポートしており、Options APIでも利用することができます。現在のコアの最新版は@vuelidate/core@2.0.0-alpha.43です。

以前のバージョンのVulidateの0.xについては下記の記事で公開しています。

VeeValidateについても本ブログで公開しています。

バリデーションとは

ユーザが入力フォームから入力した値が要件を満たしているかチェックを行うのがバリデーションです。

バリデーションはネット上で入力フォームを備えているアプリケーションでは必ず実装されており、例えばECサイトで商品を購入すると必ずメールアドレス、郵便番号や名前を入力する入力フォームが表示されます。フォームの中で入力したメールアドレスがXXX@XXXのようなメールアドレスの形式になっているか電話番号には文字ではなく適切な長さの数字が入力されているかなどのチェックを行うことをバリデーションといいます。

バリデーションにはフロントエンド側で行うバリデーションとバックエンド側で行うバリデーションがあります。本文書はVue.jsを利用しているのでフロントエンド側のバリデーションになります。サーバにデータを送信する前のデータのチェックを行います。登録ボタン等(submit)をクリックし、入力した内容がサーバに送信されサーバ側でデータのバリデーションを行うのがバックエンド側のバリデーションです。どちらか一方ではなく通常はどちらのバリデーションも利用するのでバックエンドのバリデーションの設定方法の理解も必要となります。

Vue3プロジェクトの作成

Vuelidateの動作確認を行うVue3プロジェクトの作成を行います。Vuelidateの動作確認を行うので機能の追加は行わずにプロジェクトを作成しています。


 % npm init vue@latest

Vue.js - The Progressive JavaScript Framework

✔ Project name: … vuelidate_2_test
✔ 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 Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes

Scaffolding project in /Users/mac/Desktop/vue/vuelidate_2_test...

Done. Now run:

  cd vuelidate_2_test
  npm install
  npm run dev

プロジェクト作成後、作成されるプロジェクトに移動してJavaScriptのパッケージをインストールするためにnpm installコマンドを実行します。

Tailwind CSSのインストール・設定

Tailwind CSSのインストールは必須ではありませんがVuelidateで利用する入力フォームでCSSを利用するためTailwind CSSのインストールを行います。


 % npm install -D tailwindcss postcss autoprefixer

下記のコマンドを実行することでTailwind CSSの設定ファイルtailwind.config.jsファイルが作成されます。


 % npx tailwindcss init -p

作成されたtailwind.config.jsファイルに以下を追加します。


/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{vue,js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

src¥assetsフォルダにあるmain.cssファイルに以下を追加します。main.cssファイルはmain.jsファイルからimportされています。


@tailwind base;
@tailwind components;
@tailwind utilities;

App.vueファイルでTailwind CSSの設定が完了しているか確認するために以下のコードを記述します。

入力フォームの作成

利用する入力フォームの作成を行います。リアクティブな変数を定義するためreactive関数を利用します。フォームはname, age, emailの3つで構成されておりそれぞれのinput要素にはv-modelを設定しています。

送信ボタンをクリックするとsubmitイベントによりsubmitForm関数が実行され入力された値がブラウザのコンソールに表示させるようになっています。サーバへのリクエストなどは行いません。コードが長く複雑に見えるかもしれませんがTailwind CSSを利用して設定したclassを削除すればシンプルなコードになります。


<script setup>
import { reactive } from 'vue';

const formData = reactive({
  name: '',
  age: '',
  email: '',
});

const submitForm = () => {
  console.log('submit', formData);
};
</script>

<template>
  <div class="mt-12 flex justify-center">
    <div class="w-full max-w-sm text-gray-700">
      <h1 class="text-2xl font-bold mb-2">入力フォームバリデーション</h1>
      <form class="bg-white shadow-md rounded p-4" @submit.prevent="submitForm">
        <div class="mb-4">
          <label class="bloc text-sm font-bold mb-2" for="name"> 名前 </label>
          <input
            class="border rounded w-full p-2"
            id="name"
            type="text"
            placeholder="名前"
            v-model="formData.name"
          />
        </div>
        <div class="mb-4">
          <label class="bloc text-sm font-bold mb-2" for="age">年齢 </label>
          <input
            class="border rounded w-full p-2 text-gray-700"
            id="age"
            type="age"
            placeholder="年齢"
            v-model="formData.age"
          />
        </div>
        <div class="mb-4">
          <label class="block text-sm font-bold mb-2" for="email">
            メールアドレス
          </label>
          <input
            class="border rounded w-full p-2 text-gray-700"
            id="email"
            type="email"
            placeholder="メールアドレス"
            v-model="formData.email"
          />
        </div>
        <div class="flex items-center justify-between">
          <button
            class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
            type="submit"
          >
            送信
          </button>
        </div>
      </form>
    </div>
  </div>
</template>

npm run devコマンドを実行して開発サーバを起動してブラウザからアクセスすると以下のフォームが表示されます。

作成した入力フォーム
作成した入力フォーム

フォームに入力を行い、”送信”ボタンをクリックするとProxyのTargetの中でageとemailとnameの値を確認することができます。

入力した内容の確認
入力した内容の確認

Vuelidateのインストール

Vuelidataの動作確認に利用するフォームの作成が完了したので、npmコマンドを利用してvuelidateのインストールを行います。yarn addコマンドでもインストールすることができます。


 % npm install @vuelidate/core @vuelidate/validators

Vuelidateの設定

Vuelidateを利用するためにはuseVuelidateとデータのバリデーションを行うValidatorsをimportする必要があります。Built-in Validators(Vulidateが事前に準備しているもの)としてrequiredやemail、maxLengthなどがあります。Validatorsについてはhttps://vuelidate-next.netlify.app/validators.htmlで確認することができます。

Validatorのrequiredは入力が必須の要素に設定を行うことができます。

importしたuserValidateの引数には設定したルールとデータを設定します。下記ではnameにのみrequiredのValidatorを設定しています。


import { reactive } from 'vue';
import { useVuelidate } from '@vuelidate/core';
import { required } from '@vuelidate/validators';

const formData = reactive({
  name: '',
  age: '',
  email: '',
});

const rules = {
  name: { required },
};

const v$ = useVuelidate(rules, formData);

const submitForm = () => {
  console.log('submit', formData);
};

ここまでの設定が完了しChromeブラウザのExtensionsのVue.js DevtoolでAppコンポーネントの情報を確認するとformDataだけではなくrequired, rules, v$オブジェクトを確認することができます。

Vue.js Devtoolsでコンポーネントの確認
Vue.js Devtoolsでコンポーネントの確認

その中でもComputedプロパティであるv$オブジェクトの中には$errorや$errorsなどさまざまな情報が含まれていることがわかります。同じ情報がuseVuelidateの戻り値を持つv$の中に含まれています。

v$の確認
v$の確認

入力フォームの名前にはバリデーションを設定しているので何も入力しないまま”送信”ボタンをクリックするとv$オブジェクトのプロパティに変化があるか確認します。

送信ボタンをクリックしてもほどんどのプロパティに変化がありませんが名前のフォームに文字を入力すると$invalidの値がtrueからfalseに変更することがわかります。再度文字を削除するとfalseからtrueに戻ります。

$invalidはすべてのバリデーションにパスした場合のみfalseになる値です。この値を利用することでバリデーションをパスしたかどうかチェックすることができます。


const submitForm = () => {
  if (v$.value.$invalid) {
    console.log('バリデーションエラー発生');
  } else {
    console.log('バリデーションパス、リクエスト送信');
    console.log('submit', formData);
  }
};
computedプロパティにはref関数で設定した変数の場合と同様にscriptタグ内でアクセスする場合は.valueが必要です。

v$.value.$invalidによる分岐を追加することで、名前フォームに文字列を入力しない場合にはコンソールに”バリデーションエラー発生”が表示され、文字を入力すると”バリデーションパス、リクエスト送信”が表示されます。

フォームデータの個別のバリデーションの状況についてはv$.value.name.$invalidからアクセスすることができます。v$.value.invalidはフォームデータ全体のバリデーションの状況を確認することができます。

エラー情報の取得

$invalidを値を確認することでバリデーションの状態を確認することができました。エラーが発生している場合にユーザにエラーメッセージを表示させたいのでメッセージが保存される$errorsオブジェクトの中身をsubmitFormの中で確認します。$errorsオブジェクトにはv$.value.$errorsでアクセスすることができます。


const submitForm = async () => {
  console.log('submit', formData);
  console.log('$errors',v$.value.$errors);
};

フォームに文字列を入力せずに”送信”ボタンをクリックしてもv$.value.$errorsの配列は空のままです。

エラーメッセージを取得するためには$validate関数を実行する必要があります。$validateを実行すると$dirtyの値がすべてtrueになり、すべてのバリデーションが実行されます。$dirtyは入力フィールドに対してユーザがカーソルを合わせたかどうかなどユーザが入力フィールドに対して何かアクションをしたかどうかチェックをするために利用することができます。$dirtyや$invalidを利用してエラーの状態を持つ$errorの値が決まります。


const submitForm = async () => {
  console.log('submit', formData);
  v$.value.$validate();
  console.log('$errors',v$.value.$errors);
};

$validata関数を設定した後はフォームに文字列を入力せずに”送信”ボタンをクリックするとバリデーションエラーの情報が保存されるようになります。配列になっておりバリデーションエラーのメッセージ($message)だけではなくどのプロパティ($property)でどのバリデーター($validator)でエラーなのかも確認することができます。

バリデーションに失敗した後の$errorsオブジェクトの中身
バリデーションに失敗した後の$errorsオブジェクトの中身

本当にバリデーションが動作しているのか確認するために名前のフォームに文字を入力して”送信”ボタンをクリックすると$errorsの配列は[](空)になります。メッセージではなくバリデーションエラーが発生しているかどうかは$v.value.$errorで確認することができます。

$validate関数はバリデーションの結果をboolenで戻しますがPromiseで戻されるため結果を取得するためにはasync, await関数を利用する必要があります。


const submitForm = async () => {
  const result = await v$.value.$validate();
  console.log('result', result); // true or false
};

ユーザへのエラーメッセージの表示

$errorsにエラーメッセージが含まれることがわかったので$errorsを利用してブラウザ上にメッセージを表示させる方法を確認します。

それぞれのフォームデータのプロパティのエラーについてはv$.value.プロパティ名に含まれているのでinput要素の下に表示用のdiv要素を追加します。

v$.name.$errorsは配列でエラーメッセージの情報が保存されているのでv-forで展開しています。v-forではkey属性により一意の値を設定する必要があるためerror.$uidを設定しています。


<div class="mb-4">
  <label class="bloc text-sm font-bold mb-2" for="name"> 名前 </label>
  <input
    class="border rounded w-full p-2"
    id="name"
    type="text"
    placeholder="名前"
    v-model="formData.name"
  />
  <div v-for="error of v$.name.$errors" :key="error.$uid">
    <div class="text-red-700 font-bold">{{ error.$message }}</div>
  </div>
</div>

設定後、名前フィールドに何も入力せずに”送信”ボタンをクリックするとメッセージが表示されます。

バリデーションのエラーメッセージの表示
バリデーションのエラーメッセージの表示

表示されたメッセージは文字列を入力するとバリデーションにパスしブラウザ上から消えて非表示となります。

文字を入力するとメッセージが消える
文字を入力するとメッセージが消える

再度文字を消すとメッセージが表示されます。メッセージに表示・非表示のため”送信”ボタンを再度クリックして$validate関数を実行する必要はありません。

nameだけではなくage, emailについてもバリデーションを設定します。emailではrequiredとemailを設定しています。バリデーターのemailは入力した文字列がメールアドレスの形式で入力されているかチェックを行います。requiredはimportしているのでrequire以外の別のバリデーターを追加する場合はvalidatorsからimportする必要があります。


import { reactive } from 'vue';
import { useVuelidate } from '@vuelidate/core';
import { required, email } from '@vuelidate/validators';

const formData = reactive({
  name: '',
  age: '',
  email: '',
});

const rules = {
  name: { required },
  age: { required },
  email: { required, email },
};

各input要素の下にエラーメッセージ用のdiv要素を追加します。


<h1 class="text-2xl font-bold mb-2">入力フォームバリデーション</h1>
<form class="bg-white shadow-md rounded p-4" @submit.prevent="submitForm">
  <div class="mb-4">
    <label class="bloc text-sm font-bold mb-2" for="name"> 名前 </label>
    <input
      class="border rounded w-full p-2"
      id="name"
      type="text"
      placeholder="名前"
      v-model="formData.name"
    />
    <div v-for="error of v$.name.$errors" :key="error.$uid">
      <div class="text-red-700 font-bold">{{ error.$message }}</div>
    </div>
  </div>
  <div class="mb-4">
    <label class="bloc text-sm font-bold mb-2" for="age">年齢 </label>
    <input
      class="border rounded w-full p-2 text-gray-700"
      id="age"
      type="age"
      placeholder="年齢"
      v-model="formData.age"
    />
    <div v-for="error of v$.age.$errors" :key="error.$uid">
      <div class="text-red-700 font-bold">{{ error.$message }}</div>
    </div>
  </div>
  <div class="mb-4">
    <label class="block text-sm font-bold mb-2" for="email">
      メールアドレス
    </label>
    <input
      class="border rounded w-full p-2 text-gray-700"
      id="email"
      type="email"
      placeholder="メールアドレス"
      v-model="formData.email"
    />
    <div v-for="error of v$.email.$errors" :key="error.$uid">
      <div class="text-red-700 font-bold">{{ error.$message }}</div>
    </div>
  </div>

emailにはtestの文字列を入力してnameとageに何も入力せず”送信”ボタンをクリックするとエラーメッセージが表示されます。メールのメッセージは他の2つは異なっていることも確認できます。

各フィールドでのエラーメッセージの表示
各フィールドでのエラーメッセージの表示

$errosをv-forを利用してエラーを表示させていましたが$errorを利用してエラーメッセージの表示・非表示を切り替えることもできます。


<div v-if="v$.name.$error">
  <div class="text-red-700 font-bold">
    {{ v$.email.$errors[0].$message }}
  </div>
</div>

エラーメッセージの表示とどのタイミングでメッセージが表示されるのかを理解することができました。

バリデーションの状態を持つ情報として$error, $invalidの情報しか確認していませんがその他にもvuelidate関数の時に出てきた$dirtyや$modelなどいろいろなものがあります。より複雑なバリデーションを行いたい時にはぜひ活用してください。

メッセージの日本語化

表示されているメッセージが英語になっているので日本語で表示できるように設定を行います。Vuelidateのドキュメントhttps://vuelidate-next.netlify.app/advanced_usage.html#i18n-supportを参考に設定を行っています。

npmコマンドでvue-i18n@nextのインストールを行います。


 % npm install vue-i18n@next 

srcフォルダにutlisフォルダを作成し、その下にi18n-validators.jsファイルを作成します。requiredとemailのエラーメッセージに対応する日本語を設定しています。


// @/utils/i18n-validators.js
import * as validators from '@vuelidate/validators';
import { createI18n } from 'vue-i18n';

const messages = {
  ja: {
    validations: {
      required: 'この項目{property}は必須です.',
      email: '正しいメールアドレスの形式で入力してください。',
    },
  },
};

const i18n = createI18n({
  legacy: false, // you must set `false`, to use Composition API
  locale: 'ja', // set locale
  messages, 
});

// // or import { createI18nMessage } from '@vuelidate/validators'
const { createI18nMessage } = validators;

// // Create your i18n message instance. Used for vue-i18n@9
const withI18nMessage = createI18nMessage({ t: i18n.global.t.bind(i18n) });
// for vue-i18n@8
// const withI18nMessage = createI18nMessage({ t: i18n.t.bind(i18n) })

// wrap each validator.
export const required = withI18nMessage(validators.required);
export const email = withI18nMessage(validators.email);

App.vueでは作成したi18n-validatorsからrequired, emailをimportします。


import { reactive } from 'vue';
import { useVuelidate } from '@vuelidate/core';
// import { email } from '@vuelidate/validators';
import { required, email } from './utils/i18n-validators';
//略

設定は以上で完了です。

先ほどまでとは異なりメッセージが日本語で表示されます。

日本語のメッセージの表示
日本語のメッセージの表示