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

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

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

Vue3環境の構築

Vue CLIコマンドを利用しVue 3の環境を構築します。プロジェクト名には任意の名前をつけることができるのでここではvue-vee-validateという名前をつけています。


% vue create vue-vee-validate

vee-validateのインストール

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


 % npm i vee-validate@next --save

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

vee-validateの基本動作確認

前準備

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


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

<script>
export default {};
</script>

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

前準備
前準備

input要素の追加

input要素を追加しref関数を利用してリアクティブデータnameを定義してv-modelの設定を行います。ref関数を利用するためにはimportが必要です。


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

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

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

refを利用してv-modelを設定
refを利用してv-modelを設定

VueのDevtoolsで確認するとリアクティブのデータnameを確認することができます。

Vue DevToolsで確認
Vue DevToolsで確認

vee-validateの設定

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


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

<script>
import { useField } from 'vee-validate';
export default {
  setup() {
    const name = useField('name');
    return {
      name: name.value,
    };
  },
};
</script>

useFieldから戻されたオブジェクトをnameに保存しています。name.valueの中にinputで入力した値が保存されます。useFieldから戻されるオブジェクトにはvalue以外にもさまざまなプロパティが含まれています。そのオブジェクトの中のvalueに入力値が入っているためname.valueとなります。

Vue DevToolsで確認するとRefで表示された時と同じ内容が確認できます。

Vue DevToolsで確認
Vue DevToolsで確認

useFiledから戻される値はオブジェクトなので分割代入を利用することもできます。useField関数から戻されるオブジェクトの中からvalueのみ分割代入を利用して取り出しています。valueはそのままv-modelに設定を行うことができます。


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

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

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


export default {
  setup() {
    const { value } = useField('name', (value) => {
      if (!value) {
        return 'this field is required';
      }
      return true;
    });
    return {
      value,
    };
  },
};

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

VueのComposition APIを利用する場合はtemplateタグ側にデータを渡す際はreturnするオブジェクトに含める必要があります。returnのオブジェクトの中にvalueに加えerrorMessageを追加しています。

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

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

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

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

初めてのバリデーション
初めてのバリデーション

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


export default {
  setup() {
    const isRequired = (value) => (value ? true : 'This field is required');
    const { value, errorMessage } = useField('name', isRequired);
    return {
      value,
      errorMessage,
    };
  },
};

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

yupのインストール

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


$ npm install --save yup

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


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

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

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

yupを利用したバリデーション
yupを利用したバリデーション

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

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

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


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

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

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

複数のinput要素の動作確認

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


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

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

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

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

2つのinput要素
2つのinput要素

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

メールのバリデーション
メールのバリデーション

john@exmaple.comとメール形式の文字列を入力するとエラーが表示されなくなります。2つ目のinput要素にはemailのバリデーションが設定されていることが確認できました。1つ目のinput要素の場合は必須項目だけバリデーションを設定しているので文字を入れて削除するとメッセージが表示されます。

別々のバリデーションを設定
別々のバリデーションを設定

2つのinput要素でもvee-validateが動作することが確認できました。

useFormの利用

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

バリデーションスキーマの作成を行います。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関数を利用すると最終的には下記のようなコードに書き換えることができます。


import { useField, useForm } from 'vee-validate';
import * as yup from 'yup';
export default {
  setup() {
    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');

    return {
      name,
      nameError,
      email,
      emailError,
    };
  },
};

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

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

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


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

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');

return {
  name,
  email,
  errors,
};

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のカスタイズ

エラーメッセージの中に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('メールアドレスの形式ではありません。'),
});
メッセージの内容を変更
メッセージの内容を変更

イベントの設定 handleChange

input要素に文字を入力するとすぐにバリデーションが行われます。input要素からフォーカスが外れた時にバリデーションを実行することも可能です。その場合はuseFiledが戻されるオブジェクトの中に入っているhandleChangeを利用します。


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

input要素のblurイベントを設定してhandleChangeを設定します。


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

先程までのように文字を入力してもエラーメッセージは表示されません。

handleChangeを利用
handleChangeを利用

input要素からフォーカスを外すとblurイベントによってバリデーションが実行されます。

カーソルを外すとメッセージが表示
カーソルを外すとメッセージが表示

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

@blurではなく@changeイベントに変更すると文字を入力した後にフォーカスを外した時にイベントが発火されますが何も入力せずにフォーカスを外した場合はイベントは発火されません。


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

inputイベントを設定した場合はv-modelを設定した時と同様に文字を入力した時点でバリデーションが実行されます。


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

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

useFieldで初期値の設定

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


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

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

useFieldでinput要素に初期値設定
useFieldでinput要素に初期値設定

useFormで初期値を設定

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


const formValues = {
  name: 'John',
  email: 'john@example.com',
};

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

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

useFormで初期値を設定
useFormで初期値を設定

useFieldのメタデータ

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

メタデータって?と疑問に思う人も多いかと思うのでメタデータにはどのような値が入っており、入力フォームとどうような関係があるのか確認していきましょう。

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

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


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

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

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

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

ブラウザで確認すると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に戻すことはできません。これ値を利用してinput要素に入力があったということを判定することができます。

正しいメールアドレスを入力するとvalidはtrueになります。

validがtrueに
validがtrueに

ドキュメントでのvalidの説明は、”The current field validity, automatically updated for you.”と記載されています。動作確認からバリデーションにパスしたら値がtrueになることがわかりました。メールアドレスの形式と異なる形に戻すと一度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の注意点としてドキュメントにはinitialValueを設定していない場合にmeta.dirtyが期待した通り値にならない場合があるので初期値がない場合でもinitialValueの設定する必要があると記載されているので忘れてずにinitialValueの設定を行う必要があります。

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の値を更新するために利用することができます。

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


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

return {
  name,
  email,
  errors,
  meta,
  handleBlur,
};

@inputイベントに設定することで文字を入力するとすぐにイベントが発火されhandleBlurによりmeta.touchedの値がfalseからtrueに変わります。


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

フォームの処理

通常フロントエンドの入力フォームで入力されたデータはバックエンドサーバに送信されサーバ側では送られたデータを使って決められた処理を実行します。ログインフォームの場合は入力したメールアドレスとパスワードをサーバに送信しログイン処理を行います。ここまでは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で表示しています。


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

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

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

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

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

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

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

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

handleSubmitの進捗の確認

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


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

通常はフォームで入力した値を送信する処理を行いますがここではfetch関数を利用して非同期でJSONPlaceholderからデータを取得しています。JSONPlaceHolderはJSONを戻してくれるサービスでAPIの動作確認に利用することができます。


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

const onSubmit = handleSubmit(async () => {
  const res = await fetch('https://jsonplaceholder.typicode.com/todos/');
  const data = await res.json();
  console.log(data);
});

設定後、フォームに入力して送信ボタンを押すとJSONPlaceHolderからデータを取得する間送信ボタンはクリックできない状態になります。

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

このようにisSubmittingを利用することができます。

submitカウントの取得

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

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


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

<script>
import { computed } from 'vue';
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, handleSubmit, isSubmitting, submitCount } = useForm({
      validationSchema: schema,
      initialValues: {
        name: '',
        email: '',
      },
    });

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

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

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

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

バリデーションにパスしているかどうかに関わらず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を利用してみてください。