compositionAPIでyupとvee-validateを使ったバリデーションをする

# はじめに

yupとvee-validateを用いれば簡単にフォームのバリデーションを行うことができます。
ただvee-validateは非常に便利なバリデーションライブラリなのですが、compositionAPIを用いている場合の情報があまりなかったので記事にしてみました。

# yupとは

yupはJavaScriptでフォームのバリデーションルールを宣言的に記述することのできるライブラリです。

https://github.com/jquense/yup

# vee-validateとは

vee-validateは「素早い」「柔軟」「実装が容易」といった多くの特徴を持つVue.jsのバリデーションライブラリです。

https://vee-validate.logaretm.com/v4/

# 検証環境
vue 3.0.0
TypeScript 4.1.5
vee-validate 4.5.6
yup 0.32.11

# プロジェクトの作成

vue-cliを用いてプロジェクトを作成します。
vue-cliのバージョンが4.5以上でないとvue3のセットアップができないのでアップグレードします。

```terminal
yarn global upgrade --latest @vue/cli
```

`vue create`コマンドでプロジェクトを作成し、vue3を選択します。

```terminal
vue create <your-project-name>

Vue CLI v4.5.15
? Please pick a preset: Default (Vue 3) ([Vue 3] babel, eslint)
```

作成したプロジェクトのディレクトリに移動します。

```terminal
cd <your-project-name>
```

`vue add`コマンドでTypeScriptを導入します。質問には以下のように答えてください。

```terminal
vue add typescript

? Use class-style component syntax? No
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Convert all .js files to .ts? Yes
? Allow .js files to be compiled? No
? Skip type checking of all declaration files (recommended for apps)? Yes
```

`yarn serve`でページにアクセスできたら準備完了です。

![](https://storage.googleapis.com/zenn-user-upload/ea1a4b4d63fc-20211125.png)


# ライブラリのインストール

```terminal
yarn add vee-validate@next
yarn add yup
```

上記コマンドを使用して`yup`と`vee-validate`をインストールします。
vee-validateは4系でないとVue3に対応していないので注意してください。

# バリデーションの実装

`App.vue`を編集してフォームを作ります。

```html:src/App.vue
<template>
  <div class="container">
    <form>
      <div class="input-field">
        <input type="text" v-model="text" />
        <p class="error-message">{{ textError }}</p>
      </div>
      <button type="submit">送信</button>
    </form>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  setup() {
    const text = ref<string>("");
    return {
      text,
    };
  },
});
</script>

<style scoped>
.container {
  padding: 10px;
  width: 300px;
}
.input-field {
  margin-bottom: 16px;
}
.error-message {
  height: 16px;
  color: red;
}
</style>
```

<img width="200" src="https://storage.googleapis.com/zenn-user-upload/85a4f1afb42c-20211125.png">

`vee-validate`と`yup`をimportしてバリデーションスキーマを作成します。

```ts:src/App.vue
import { defineComponent } from "vue";
import { useField, useForm } from "vee-validate";
import * as yup from "yup";

export default defineComponent({
  setup() {
    const formSchema = yup.object({
      text: yup.string().required('テキストは必須項目です')
    })

    useForm({ validationSchema: formSchema })

    const { value: text, errorMessage: textError } = useField<string>("text");

    return {
      text,
      textError,
    };
  },
});
```

ここでフォームのスキーマを定義しています。こちらの記述は`text`はstring型の必須項目という意味です。またrequiredの中に任意のテキストを指定し、バリデーションエラーメッセージを作成することもできます。このように`yup`を用いるとバリデーション関数をとても簡単に作成することができます。

```ts
const formSchema = yup.object({
  text: yup.string().required('テキストは必須項目です')
})
```

そしてここで作成したスキーマをフォームに適用しています。

```ts
useForm({ validationSchema: formSchema })
```

最後に`useField`関数を用いてリアクティブな変数を定義しています。テキストフォームに入力された値が`text`に代入され、バリデーションエラーが発生すると`errorMessage`にエラーメッセージが代入されます。

```ts
const { value: text, errorMessage: textError } = useField<string>("text");
```

<img width="200" src="https://storage.googleapis.com/zenn-user-upload/5b02a143db90-20211125.gif">

formタグにsubmitイベントを付ければバリデーションエラー発生時にデータを送信させないようにすることもできます。

```diff_html:src/App.vue
  <div class="container">
+   <form @submit="handleSubmit">
      <div class="input-field">
        <input type="text" v-model="text" />
        <p class="error-message">{{ textError }}</p>
      </div>
      <button type="submit">送信</button>
    </form>
  </div>
```

```diff_typescript:src/App.vue
+ const handleSubmit = (e: Event) => {
+   if (text.value && !textError.value?.length) {
+   return true
+ }
+   text.value = "";
+   e.preventDefault()
+ }

return {
  text,
  textError,
+ handleSubmit,
};
```

<img width="200" src="https://storage.googleapis.com/zenn-user-upload/e022436edae8-20211125.gif">

# アカウントIDのバリデーション
ここからはWEBアプリでよくあるフォームのバリデーション実装例を紹介します。

```diff_html:src/App.vue
  <div class="container">
    <form @submit="handleSubmit">
      <div class="input-field">
        <input type="text" v-model="text" />
        <p class="error-message">{{ textError }}</p>
      </div>
+     <div class="input-field">
+       <div><p>アカウントID</p></div>
+       <input type="text" v-model="accountId" />
+       <p class="error-message">{{ accountIdError }}</p>
+     </div>
      <button type="submit">送信</button>
    </form>
  </div>
```

アカウントIDのフォームを追加します。

<img width="200" src="https://storage.googleapis.com/zenn-user-upload/1f2cb5330529-20211126.png">

アカウントIDは下記のように要件が決まっています。
**半角英数字をそれぞれ1種類以上含み8文字以上32文字以下であること**

このバリデーションを実現するには`matches`を用います。

```diff_typescript:src/App.vue
const formSchema = yup.object({
  text: yup.string().required("テキストは必須項目です"),
+ accountId: yup
+   .string()
+   .matches(
+     /^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,32}$/i,
+     "アカウントIDの形式が間違っています。"
+   ),
});
```

このように`matches`を用いることでフォームに入力された値が正規表現にマッチするかバリデーションすることができます。あとは残りのコードを記述します。

```diff_typescript:src/App.vue
+ const { value: accountId, errorMessage: accountIdError } = useField<string>("accountId");

const handleSubmit = (e: Event) => {
  if (
    text.value &&
    !textError.value?.length &&
+   accountId.value &&
+   !accountIdError.value?.length
  ) {
    return true;
  }
  text.value = "";
+ accountId.value = "";
  e.preventDefault();
};

return {
  text,
  textError,
+ accountId,
+ accountIdError,
  handleSubmit,
};
```

<img width="200" src="https://storage.googleapis.com/zenn-user-upload/555276f637ba-20211126.gif">

# パスワード一致のバリデーション

2つのフォームにパスワードを入力した時、パスワードが一致するかをバリデーションします。
まずは２つのフォームを追加します。なお今回は説明のためinputタグのtypeはtextで行います。（本来はpasswordにするべき）

```diff_html:src/App.vue
  <div class="container">
    <form @submit="handleSubmit">
      <div class="input-field">
        <input type="text" v-model="text" />
        <p class="error-message">{{ textError }}</p>
      </div>
      <div class="input-field">
        <div><p>アカウントID</p></div>
        <input type="text" v-model="accountId" />
        <p class="error-message">{{ accountIdError }}</p>
      </div>
+     <div class="input-field">
+       <div><p>パスワード</p></div>
+       <input type="text" v-model="password" />
+       <p class="error-message">{{ passwordError }}</p>
+     </div>
+     <div class="input-field">
+       <div><p>パスワード確認用</p></div>
+       <input type="text" v-model="passwordConfirm" />
+       <p class="error-message">{{ passwordConfirmError }}</p>
+     </div>
      <button type="submit">送信</button>
    </form>
  </div>
```

<img width="200" src="https://storage.googleapis.com/zenn-user-upload/1a5b41658cfc-20211126.png">

バリデーションスキーマは以下のように記述します。

```diff_typescript:src/App.vue
 const formSchema = yup.object({
      text: yup.string().required("テキストは必須項目です"),
      accountId: yup
        .string()
        .matches(
          /^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,32}$/i,
          "アカウントIDの形式が間違っています。"
        ),
+     password: yup.string().required("パスワードは必須項目です"),
+     passwordConfirm: yup
+       .string()
+       .required("パスワード確認用は必須項目です")
+       .oneOf([yup.ref("password")]),
    });
```

ポイントは`oneOf([yup.ref("password")])oneOf([yup.ref("password")])`の部分です。`oneOf`は引数として与えられた配列の文字列に完全一致しているかをバリデーションできます。ここに`yup.ref("password")`を与えることで`password`と`passwordConfirm`が一致するかをテストすることができます。残りのコードも記述していきます。

```diff_typescript:src/App.vue
const { value: text, errorMessage: textError } = useField<string>("text");
const { value: accountId, errorMessage: accountIdError } =useField<string>("accountId");
+ const { value: password, errorMessage: passwordError } =useField<string>("password");
+ const { value: passwordConfirm, errorMessage: passwordConfirmError } =useField<string>("passwordConfirm");


const handleSubmit = (e: Event) => {
  if (
    text.value &&
    !textError.value?.length &&
    accountId.value &&
    !accountIdError.value?.length &&
+   password.value &&
+   !passwordError.value?.length &&
+   passwordConfirm.value &&
+   !passwordConfirmError.value?.length
  ) {
    return true;
  }
  text.value = "";
  accountId.value = "";
+ password.value = "";
+ passwordConfirm.value = "";
  e.preventDefault();
};

return {
  text,
  textError,
  accountId,
  accountIdError,
+ password,
+ passwordError,
+ passwordConfirm,
+ passwordConfirmError,
  handleSubmit,
};
```

<img width="200" src="https://storage.googleapis.com/zenn-user-upload/1e1cc23ba78e-20211126.gif">

# おわりに
yupとvee-validateを組み合わせることで簡単にバリデーション機能を実装することができます！またcompositionAPIで記述することでより、見通しの良いコードが書けます。興味が湧いた方は是非試してみてください。

# 参考
https://vee-validate.logaretm.com/v4/
https://github.com/jquense/yup
https://reffect.co.jp/vue/veevaliate4