【Vue】VeeVallidateによるカスタムバリデーションの作成

## はじめに
「サーバーへリクエスト送信する前に、フロント（Vue）側でバリデーションをかけるには何を実装すればいいんだ？」と疑問を持たれている方向けに投稿しました。

## 開発環境
|言語(FW,ライブラリ)|バージョン|
|---|---|
|vue.js  |2.6.14  |
|nuxt.js/composition-api  |^0.33.1 |
|vee-validate  |3  |
|node  |16.11.0  |

## vee-vallidateとは
Vue.jsでリアルタイムバリデーションを行うためのライブラリです。
入力時に不正な値があった場合、サーバーへ送信する前にエラーを表示させることができます。

## 実装内容
ユーザー登録時のパスワード入力のバリデーションエラーを表示する。

### 仕様
- パスワードには**大文字・小文字・数字**がそれぞれ1文字以上含まれていること。含まれていなかったものがあれば、バリデーションエラーを返す
- パスワードは**8文字以上16文字以内**で入力されていること。8文字未満もしくは16文字を超えた場合はバリデーションエラーを返す

### コードの例
説明の関係上で一部省略しておりますので、ご了承ください🙏
#### ユーザー登録フォーム（必須項目のみ抜粋）
```components/molecules/userForm.vue
<template>
  <validation-observer ref="observer" v-slot="{ invalid }">
    <validation-provider
      v-slot="{ errors }"
      rules="'required|email'"
      name="メールアドレス"
    >
      <v-text-field v-model="newUser.email" :error-messages="errors"></v-text-field>
    </validation-provider>
    
    <validation-provider
      v-slot="{ errors }"
      :rules="{ required: true, minmax: { min: 6, max: 16 }, password_regex }"
      name="パスワード"
     >
      <v-text-field v-model="newUser.password" type="password" :error-messages="errors" hint="パスワードには大文字、小文字、数字、記号を含む少なくとも8文字以上の入力が必須です"></v-text-field>
    </validation-provider>
  </validation-observer>
</template>

<script lang="ts">
  import { defineComponent, ref, reactive } from '@vue/composition-api'

  
  export default defineComponent({

    setup(props, { emit }) {
      interface newUser {
        email: string
        password: string
      }

      const newUser = reactive<newUser>({ email: '', password: '' })
      const observer = ref()
  
      return {
        newUser,
        observer
      }
    }
  })
</script>

```

##### 補足
- [`validation-observer`](https://vee-validate.logaretm.com/v2/guide/components/validation-observer.html) :フォーム全体の検証結果を管理し、有効な状態かどうかを判断します
- [`validation-provider`](https://vee-validate.logaretm.com/v2/guide/components/validation-provider.html) :フォーム内で個別に検証ルールを適用して有効かどうかを判断します
- [`v-slot`](https://ja.vuejs.org/guide/components/slots):親コンポーネントから子コンポーネントへコンテンツ（`invalidプロパティ`）を渡します
- [`invalid`](https://vee-validate.logaretm.com/v3/guide/state.html#flags): trueの時、フォームにエラーがあり、送信できない状態（falseはその逆）
- [`rules`](https://vee-validate.logaretm.com/v2/guide/rules.html): 各フィールドのバリデーションのルールを定義しています。例えば[required](https://vee-validate.logaretm.com/v2/guide/rules.html#validation-rules)は、veeValidate（ライブラリ）で標準の機能として用意されています。
もし用意されていなければ[カスタムルール](https://vee-validate.logaretm.com/v2/guide/custom-rules.html#creating-a-custom-rule)を自分で作成する必要があります。

#### カスタムのバリデーション
```plugin/vee-validate.js
import Vue from 'vue'
import {
  ValidationProvider,
  ValidationObserver,
  localize,
  extend,
} from 'vee-validate'
import ja from 'vee-validate/dist/locale/ja.json'
import * as rules from 'vee-validate/dist/rules'

for (const rule in rules) {
  extend(rule, rules[rule])
}

extend('password_regex', {
  validate(value) {
    const regex = /(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)/.test(value);

    return regex
  },
  message:
    '英大文字、英小文字、数字を含む8文字以上の入力が必須です',
})

extend('minmax', {
  validate(value, params) {
    const length = value.length;

    return length >= params.min && length <= params.max;
  },
  params: ['min', 'max'],
  message: "{_field_}は8文字以上16文字以内で入力してください"
});

Vue.component('ValidationProvider', ValidationProvider)
Vue.component('ValidationObserver', ValidationObserver)
localize('ja', ja)
```

##### 補足
- [`extend`](https://vee-validate.logaretm.com/v3/api/extend.html#extend): カスタムルールを追加する際に使用されるVeeValidateの関数です。Vueアプリケーション内のどのコンポーネントからでもグローバルに利用することができます
- [`validate`](https://vee-validate.logaretm.com/v3/api/validate.html#validate): この関数で非同期的に入力値を即座にチェックします
- [`params`](https://vee-validate.logaretm.com/v3/guide/basics.html#rule-arguments): 各フィールドで指定した引数名をプロパティとして定義し、バリデーションロジック内でパラメータとして受け取ります。今回の場合はコンポーネントのrulesに指定したminとmaxを指定しています
- [`value`](): バリデーションの対象となる値です。フィールドに入力された文字列などを指します

### 結果
ダイアログを開いてフォーム入力し、バリデーションが発生してから、入力したデータの送信が可能になるまでの流れを画像でまとめました。

1. パスワード未入力時（hint）
![スクリーンショット 2024-08-19 0.19.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677000/4c92f189-f0af-da0d-0c1d-ad350cd75332.png)

2. パスワード未入力時（required）
![スクリーンショット 2024-08-19 0.53.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677000/9af069f0-e72b-c6c7-c383-9d8aac4ef22e.png)

3. パスワードのバリデーション（8文字以上16文字以内）
![スクリーンショット 2024-08-19 0.24.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677000/5bf55372-c160-93ff-775f-c2235f369a4b.png)

4. パスワードのバリデーション（大文字・小文字・数字を含む）
![スクリーンショット 2024-08-19 0.24.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677000/47ea607f-db88-a048-9f05-374bba7d8f2f.png)

5. バリデーションなし（フォーム送信可能）
![スクリーンショット 2024-08-19 0.25.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677000/7f4b8d6c-796f-c5db-f36d-e9d9616eda56.png)

## まとめ
- Vue.jsでカスタムのバリデーションを作成する際に、VeeValidateでリアルタイムバリデーションが実装できる
- 即座に入力ミスの結果がフィードバックされるため、ユーザーにとっては作業の効率化やユーザーエクスペリエンスの向上に繋がる

## 参考記事
- [VeeValidate（公式）](https://vee-validate.logaretm.com/v4/)
- [ルール引数(vee-validate)](https://vee-validate.logaretm.com/v3/guide/basics.html#adding-rules)
- [Text fields(Vuetify)](https://vuetifyjs.com/en/components/text-fields/#text-fields)
- https://sakitadaiki.hatenablog.com/entry/2021/04/07/112440