本文書はVue 2環境で動作確認を行っています(Vuelidate 0.x)。Vue 3の場合はVuelidate 2を利用する必要があります。Vuelidate 2は2021/9/15現在alpha版です。

本文書では開発したアプリケーションを世に出す時には避けて通れないバリデーションについて解説しています。エラー処理の設定には手間がかかってあまり好きではないなという人も多いとは思います。Vuelidateを利用するとバリデーションの設定がなくなるわけではありませんがぜひこの機会にVue.jsのVuelidateのバリデーションをマスターしてください。

Vue.jsにはさまざまなバリデーションのライブラリがありますが、その中でもVuelidateとVeeValidateが有名です。本文書ではVuelidateを利用してvue.jsでのバリデーション方法を確認していきます。

Vuelidateの公式マニュアルを読んで少し複雑でわかりにくいなと感じた人にぜひ読んでもらいたい文書です。最初は複雑だと感じるかもしれませんが一つ一つが必要な設定なので理解すれば便利だなという機能ばかりです。

バリデーションとは

ユーザが入力フォームから入力した値が要件を満たしているかチェックを行うのがバリデーションです。日本語では確認という意味を持っています。

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

バリデーションにはフロントエンド側で行うバリデーションとバックエンド側で行うバリデーションがあります。今回はvue.jsを利用しているのでフロントエンド側のバリデーションになります。本ブログサイトでも度々出てくるLaravelフレームワークで行うバリデーションはバックエンドのバリデーションです。バックエンドのバリデーションでは登録ボタン等(submit)をクリックした入力した内容がサーバに送信されサーバ側でデータのバリデーションが行われます。どちらか一方ではなく通常はどちらのバリデーションも利用するのでバックエンドのバリデーションの設定方法の理解も必要となります。

Vuelidateを使用する環境の構築

手元のパソコンでも簡単に動作確認できるようにVuelidate, vue.jsはcdnを使って読み込みます。フォームを作成するのにbootstrapを利用しています。bootstrapもcdnを使いますがフォームの見栄えを少しよくするために使用しているだけなので利用は必須ではありません。


<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" >
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

vuelidateではvalidators.min.jsとvuelidate.min.jsの2つを利用します。Vuelidateのバージョンは0.7.4を利用しています。


<script src="https://cdn.jsdelivr.net/npm/vuelidate@0.7.4/dist/validators.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuelidate@0.7.4/dist/vuelidate.min.js"></script>

任意の場所にindex.htmlファイルを作成します。

入力フォームの設定

入力フォームはbootstrapを使って作成します。htmlのコードは下記の通りです。まだvue.jsの設定は何も行なっていない通常のHTML文で作成しています。


<div id="app" class="container mt-5">
  <h2>入力フォームバリデーション</h2>
  <div class="row">
    <div class="col-sm-8">
      <form>
        <div class="form-group">
          <label for="name">名前:</label>
          <input type="name" class="form-control" id="name">
        </div>
        <div class="form-group">
          <label for="age">年齢:</label>
          <input type="age" class="form-control" id="age">
        </div>
        <div class="form-group">F
          <label for="email">メールアドレス:</label>
          <input type="email" class="form-control" id="email">
        </div>
        <button type="submit" class="btn btn-info">送信</button>
      </form>
    </div>
  </div>
</div>

ブラウザで確認すると下記のフォームが表示されます。

boostratpで入力フォーム
boostratpで入力フォーム

vue.jsの初期設定

入力フォーム作成後、vue.jsの設定を行うため、scriptタグをbodyの閉じタグの前に追加します。vue.jsにデータプロパティname, age , emailを追加してv-modelでHtml側の各input要素と結びつけます。またsubmitFormメソッドも追加しています。


<script>
  const app = new Vue({
    el: '#app',
    data: {
      title: '入力フォームバリデーション',
      name: '',
      age: '',
      email: '',
    },
    methods: {
      submitForm() {
        console.log('submit');
      }
    }
  });
</script>
vue.jsで設定を行っているtitleプロパティはvuelidateとは関係ありませんが、vue.jsの動作確認を行うために追加しています。そのためvuelidateへの影響はありません。h1タグを<h1>{{ title }}</h1>で記述してvue.jsで設定したtitleが表示されるようにしています。

html側は下記のように更新を行います。


<div id="app" class="container mt-5">
  <h2>{{ title }}</h2>
  <div class="row">
    <div class="col-sm-8">
      <form @submit.prevent="submitForm">
        <div class="form-group">
          <label for="name">名前:</label>
          <input type="name" class="form-control" id="name" v-model="name">
        </div>
        <div class="form-group">
          <label for="age">年齢:</label>
          <input type="age" class="form-control" id="age" v-model="age">
        </div>
        <div class="form-group">
          <label for="email">メールアドレス:</label>
          <input type="email" class="form-control" id="email" v-model="email">
        </div>
        <button type="submit" class="btn btn-info">送信</button>
      </form>
    </div>
  </div>
</div>

ブラウザに表示される画面は先ほどと違いがありませんが、formにsubmitイベントを設定しているので”送信”ボタンをクリックするとブラウザのデベロッパーツールのコンソールにsubmitの文字列が表示されることが確認できます。またVue Devtoolを利用してデベロッパーツールのVueタブでdataプロパティの中身が表示されることを確認しておきます。

下記の内容を表示させるためには、Chromeの拡張機能のvue devtoolのインストールが必要になります。
dataプロパティの中身をデベロッパーツールで確認
dataプロパティの中身をデベロッパーツールで確認

Vuelidateの設定

Vuelidateの初期設定

vuelidateの初期設定については公式のドキュメントを参考に行います。npmでインストールして利用する場合とcdnを使った場合では設定方法は異なりますが本文書ではcdnを使った場合のみ記述しています。

vuelidateライブラリをvue.jsのプラグインとして利用するためにVue.useをつかてグローバルに読み込みます。


<script>
  Vue.use(window.vuelidate.default);
  const { required, email } = window.validators;

  const app = new Vue({
    el: '#app',
    //略

入力フォームでは必ず入力しなければならない必須項目やアカウント作成を使う際に必ず必要となるメールアドレスに対して頻繁にバリデーションが利用されます。今回作成した入力フォームでも必須項目とメールアドレスを入力する項目があるのでvalidatatorsからバリデーションに利用したいバリデーターrequired, emailを読み込んでいます。

requiredは入力が必須な項目をチェッックするバリデーターでemailはメールアドレスチェックに利用するバリデーターです。requiredの他に最小の長さのチェックに使うminValueや文字列の長さのチェックに使うminLengthなどさまざまなものが事前に準備されています。利用するバリデーターを増やしたい場合は、{required, email }の中に追加していくことで利用できるバリデーターを増やすことができます。

{ }の中にないバリデーターを利用するとエラーが発生します。作成した入力フォームで利用したいバリデーターを{}の中に入れる必要があり、{}に入っていないバリデーターは利用できません。

Vuelidateで事前準備されているバリデーターについては公式ドキュメントに公開されているので確認してください。

事前準備されているバリデーター
事前準備されているバリデーター

Vuelidate設定による動作の確認

Vuelidateを設定することによってどのようにバリデーションに関する項目に影響があるのか確認していきます。

vue.jsにvalidationsオプションを追加します。nameプロパティに関してのみバリデーターrequiredの設定を行います。


Vue.use(window.vuelidate.default);
const { required, email } = window.validators;

const app = new Vue({
  el: '#app',
  data: {
    title: '入力フォームバリデーション',
    name: '',
    age: '',
    email: '',
  },
  validations: {
    name: {
      required
    }
  },

const {required, email} = window.validatorsでバリデーターの設定を行わない場合は下記のように記述することでもバリデーションのrequiredを利用することは可能です。


validations: {
    name: {
        required: validators.required
    }
},

設定が完了したら、デベロッパーツールからVueタグを開いてください。validationsオプションをvue.jsに追加したことで新たにcomputedプロパティに$vオブジェクトが表示されます。$vオブジェクトの中にrequiredの設定を行なったnameを確認することができます。

$vオブジェクの確認
$vオブジェクの確認

nameオブジェクト部分のみ拡大して確認します。色々なプロパティが入っていますがその中でも$model, $invalid, requiredの3つに注目します。

nameの拡大
nameの拡大

これらの値はブラウザ上でフォームに入力を行うと即座に変化するので、それらの値を見ながらブラウザ上の入力フォームの名前にJohn Doeを入力します。

入力フォームに入力後のnameオブジェクト
入力フォームに入力後のnameオブジェクト

注目していた3つの値を確認すると$modelにはブラウザ上で入力したJohn Doeが入り、$invalidはtrueからfalseにrequiredはfalseからtrueに変化していることがわかります。

requiredは必須入力のバリデーターを表しているので項目に文字を入力することでfalseからtrueに変更になることがわかります。$invalidがtrueからfalseに変更になることでnameに関するバリデーション(ここではrequired)をクリアしたことを表しています。

これらの値を確認することでユーザへのエラーメッセージの表示等の制御を行うことができます。

$invalidの確認方法

デベロッパーツールのvueのdevtool中で$invalidの値を確認することができましたがindex.htmlファイルのscriptタブの中のJavaScriptのコードでどのように$invalidの値を取得するか確認します。

送信ボタンをクリック後に実行されるsubmitFormメソッドにconsole.logを追加します。


methods: {
  submitForm() {
    console.log(this.$v.name.$invalid)
    console.log('submit');
  }
}

入力フォームに何も入力していない状態で送信ボタンを押すとコンソールには、trueとsubmitが表示されます。入力フォームに名前を入力して送信ボタンを押すとfalseとsubmitが表示されることを確認することができます。JavaScript上ではthis.$v.name.$invalidの中に$invalidの値が入っていることがわかります。

$v.name.$invalidではnameに関するバリデーションのみ確認を行うことができますがnameを含めたすべての項目のバリデーションの確認には$v.$invalidを使うことができます。入力フォームの1つの項目でもバリデーションをクリアしていない場合(通過していない)はエラーが表示されるように変更しておきます。バリデーションを通過した場合は、バックエンドサーバへのデータ送信などに関する処理を追加します。


methods: {
  submitForm() {
    if (this.$v.$invalid) {
      console.log('バリデーションエラー');
    } else {
      // データ登録の処理をここに記述 例)バックエンドサーバへのデータ送信
      console.log('submit');
    }
  }
}

$invalidを利用することでどのようなことに活用できるかをこれから確認していきます。

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

HTML上では、$v.name.requiredの値をチェックすることで名前の項目に入力が行われたどうか確認することができます。この値を利用して、ブラウザ上にメッセージを表示させることができます。

v-ifディレクティブと$v.name.requiredの値を利用します。名前に何も入力していない場合は、$v.name.requiredの値はfalseなので!false=trueになり、メッセージが表示されます。入力を行うと値はtrueになるので!true = falseにより、メッセージは非表示になります。


<div class="form-group">
    <label for="name">名前:</label>
    <input type="name" class="form-control" id="name" v-model="name">
    <span v-if="!$v.name.required">名前が入力されていません。</span>
</div>

入力前だと何も入力が行われていないため$v.name.requiredはfalseに設定されているためメッセージが表示されます。

入力前なのでメッセージが表示
入力前なのでメッセージが表示

入力を行うとメッセージが非表示になります。

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

送信ボタンの制御

バリデーションにエラーがある場合は、送信ボタンを押せない状態にすることも可能です。disabled属性を利用します。


<button :disabled="$v.$invalid" type="submit" class="btn btn-info">送信</button>

ボタン自体は表示されますが、バリデーションを通過するまではボタンを押すことはできません。

エラーメッセージ表示のタイミング

ここまでの設定では、ブラウザで入力フォームにアクセスすると何も入力していないのにエラーメッセージが表示されています。

入力前なのでメッセージが表示
何もしていないのにメッセージが表示

アクセス直後からメッセージが表示されているのではなく入力後にメッセージが表示されるように変更を行います。

デベロッパーツールのnameオブジェクトの中にあった$dirtyプロパティを利用します。

nameの拡大
nameの拡大

$dirtyプロパティは、nameに何か入力が行われると$dirtyの値がfalseからtrueに変更になります。しかし、$invalidとは異なりこの値は自動ではfalseからtrueへの変更が行われません。この値を変更するためには$v.name.touch()が必要になります。inputイベントを利用して$v.name.touch()を実行し、$dirtyの値を変更します。


<input type="name" class="form-control" id="name" v-model="name" @input="$v.name.$touch()">

$v.name.$touch()設定後に入力を行うと文字を入れた直後に$dirtyの値がfalseからtrueになります。

$dirtyの値が変わる
$dirtyの値が変わる
dirtyという単語には汚れという意味があるので、データを入力することによって$dirtyがfalseのクリーンの状態から入力が行われた(汚れた)状態であるtrueへの変化を表していると考えることができます。

もう一つnameオブジェクトには重要なプロパティ$errorがあります。$errorの値は$invalidと$dirtyの2つの値の組み合わせで、どちらかがfalseであればfalse、どちらもtrueであればtrueの値をとります。

$errorプロパティを使うことでブラウザでアクセス直後にはメッセージを表示させない制御を行うことが可能になります。


<input type="name" class="form-control" id="name" v-model="name" @input="$v.name.$touch()">
<span v-if="$v.name.$error">名前が入力されていません。</span>
アクセス直後は$dirtyはfalseで$invalidはtrueなので$errorはfalseのためメッセージは表示されません。入力後は$dirtyがtureで$invalidがfalseになり$errorがfalseになることでメッセージが表示されません。一度文字を入力した後に入力した文字をすべて削除すると$dirtyはtrue、$invalidがtrueのため$errorがtrueになりエラーが表示されます。
入力前はメッセージは表示されない
入力前はメッセージは表示されない

エラーメッセージ表示のタイミング(2)

nameのみを利用してきましたが、次はemailを使ってバリデーションを行なっていきます。

validationsオプションにemailのバリデーションを追加します。バリデーターにはrequired, emailを設定します。


validations: {
    name: {
        required
    },
    email: {
        required,
        email
    }
},

バリデーターを2つ設定したので、バリデーターごとに異なるメッセージが表示させるようにメッセージは2つに分けます。アクセス直後にメッセージが表示されないように$v.email.$touch()と$v.email.$errorを使います。


<div class="form-group">
  <label for="email">メールアドレス:</label>
  <input type="email" class="form-control" id="email" v-model="email" @input="$v.email.$touch()">
  <div v-if="$v.email.$error">
    <span v-if="!$v.email.required">メールドレスが入力されていません。</span>
    <span v-if="!$v.email.email">メールアドレスの形式が正しくありません。</span>
  </div>
</div>
入力前はメッセージは表示されない
入力前はメッセージは表示されない

しかし、メールアドレスに入力した瞬間にエラーメッセージが表示されます。バリデーションは入力した瞬間から開始され入力直後で入力中のため文字数が少ない場合でもメールアドレスの形式のバリデーションを通過しないためにエラーが表示されます。

jを入れた瞬間にエラーメッセージが表示
文字jを入れた瞬間にエラーメッセージが表示

入力した瞬間ではなく入力が完了して、カーソルを外した際にメッセージが表示されるように変更を行います。@inputではなく@blurイベントに変更します。


<input type="email" class="form-control" id="email" v-model="email" @blur="$v.email.$touch()">

文字列を途中までいれてカーソルを外した瞬間にバリデーションが行われメールアドレスに形式に沿っていない場合はメッセージが表示されます。

カーソルを外すとメッセージが表示
カーソルを外すとメッセージが表示
john@example.comなど正しいメールアドレスを入力した場合はエラーメッセージは表示されません。

classの設定(エラーを目立たせる設定)

バリーデーションがエラーの入力項目については、class属性のバインドを利用してスタイルを変更することでユーザがどの項目がエラーになっているかを簡単に識別できるようにします。

$v.name.$errorがtrueの場合はクラスのerrorが適用され、falseの場合は適用されません。


<input 
  type="email"
  id="email" 
  v-model="email" 
  @blur="$v.email.$touch()"
  :class="{ error : $v.email.$error,'form-control': true }"
>

errorクラスにはstyleタグを追加し下記のスタイルを適用します。


<style>
    .error {
	color: #8a0421;
	border-color: #dd0f3b;
	background-color: #ffd9d9;
}
</style>

メールアドレスの形式が正しくない場合は、カーソルをinput要素から外すと背景の色が変わります。

バリデーションがエラーの場合はクラスを適用
バリデーションがエラーの場合はクラスを適用

名前にも同様にerrorの設定を行ってください。


<input 
  type="name" 
  id="name" 
  v-model="name" 
  @blur="$v.name.$touch()"
  :class="{ error : $v.name.$error,'form-control': true }"
>

送信ボタンの変更

送信ボタンについては:disabledと$v.invalidを利用して、バリデーションにエラーがある場合はクリックを行うことができないように制御しました。

送信ボタンの無効化だとわかりにくい場合があるので、送信ボタンを押してバリデーションにエラーがある場合は、各inputの下にエラーメッセージが表示されるように変更します。

ボタンから:disableを削除します。


<button type="submit" class="btn btn-info">送信</button>

submitFormメソッドにthis.$v.$touch()は追加します。追加することで送信ボタンを押すとすべての入力項目の$dirtyがtrueになります。


methods: {
  submitForm() {
    this.$v.$touch();
    if (this.$v.$invalid) {
      console.log('バリデーションエラー');
    } else {
      // データ登録の処理をここに記述
      console.log('submit');
    }
  }
}

実際にブラウザでアクセス後に送信ボタンを押すとエラーメッセージが表示されます。必須項目に何も入力しない場合は送信ボタンを押すことでthis.$v.$touchが実行され一度何か入力したのと同じ状態になります。

送信ボタンを押すとエラーメッセージが表示
送信ボタンを押すとエラーメッセージが表示
ageについてはバリデーションを設定していないのでエラーメッセージは表示されません。

まとめ

ここまでの読み終えるとVuelidateの基本的な使用方法は理解できたかと思います。今回作成したコードは下記となります。index.htmlを作成し、コピー&ペーストするだけでVuelidateの動作確認を行うことができます。

下記のコードではname, emailだけではなくageにもバリデーションを追加しています。バリデーターにはrequired, integerを設定しています。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vuelidate@0.7.4/dist/validators.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vuelidate@0.7.4/dist/vuelidate.min.js"></script>
  <style>
    .error {
      color: #8a0421;
      border-color: #dd0f3b;
      background-color: #ffd9d9;
    }
  </style>
</head>
<body>
  <div id="app" class="container mt-5">
    <h2>{{ title }}</h2>
    <div class="row">
      <div class="col-sm-8">
        <form @submit.prevent="submitForm">
          <div class="form-group">
            <label for="name">名前:</label>
            <input type="name" id="name" v-model="name" @blur="$v.name.$touch()"
              :class="{ error : $v.name.$error,'form-control': true }">
            <span v-if="$v.name.$error">名前が入力されていません。</span>
          </div>
          <div class="form-group">
            <label for="age">年齢:</label>
            <input type="age" id="age" v-model="age" @blur="$v.age.$touch()"
              :class="{ error : $v.age.$error,'form-control': true }">
            <div v-if="$v.age.$error">
              <span v-if="!$v.age.required">年齢が入力されていません。</span>
              <span v-if="!$v.age.integer">整数の数字以外は入力できません。</span>
            </div>
          </div>
          <div class="form-group">
            <label for="email">メールアドレス:</label>
            <input type="email" id="email" v-model="email" @blur="$v.email.$touch()"
              :class="{ error : $v.email.$error,'form-control': true }">
            <div v-if="$v.email.$error">
              <span v-if="!$v.email.required">メールドレスが入力されていません。</span>
              <span v-if="!$v.email.email">メールアドレスの形式が正しくありません。</span>
            </div>
          </div>
          <button type="submit" class="btn btn-info">送信</button>
        </form>
      </div>
    </div>
  </div>
  <script>
    Vue.use(window.vuelidate.default);
    const { required, email, integer } = window.validators;

    const app = new Vue({
      el: '#app',
      data: {
        title: '入力フォームバリデーション',
        name: '',
        age: '',
        email: '',
      },
      validations: {
        name: {
          required
        },
        email: {
          required,
          email
        },
        age: {
          required,
          integer
        }
      },
      methods: {
        submitForm() {
          this.$v.$touch();
          if (this.$v.$invalid) {
            console.log('バリデーションエラー');
          } else {
            // データ登録の処理をここに記述
            console.log('submit');
          }
        }
      }
    });
  </script>
</body>
</html>