[Vue]Composition API + Yupを利用したフォームのバリデーション

2022.08.08
  • t_o_dと申します。
  • フォーム検証は非常に重要な処理ですが、厳格かつ適切に実装しようとなると相応の知識が必要となり、大抵の開発ではライブラリを用いることが多いです。
  • 今回はYupというライブラリを利用してVue.js(Composition API)で実装する方法を記録いたします。

結果

  • 今回は以下のバリデーションができる入力フォームを作成していきます。
    • テキストボックス
    • セレクトボックス
    • ラジオボタン

  • 必須入力だけではなく、形式チェックもYupの機能を利用いたします。

  • 全て通ったらエラーメッセージが消え、送信処理が送れます。

環境

  • mac OS Monterey 12.2
  • Node.js v18.0.0
  • Vue 3.2

手順

Vue3 + yup環境構築

  • 以下のコマンドをうち、Vue3及びyupを利用できるサンプルプロジェクトを作成
# サンプルプロジェクト作成
npm init vite@latest form-sample -- --template vue

cd form-sample

# ライブラリインストール
npm i yup

# ローカルサーバー起動
# localhost:5173を確認
npm run dev

内容の記述

  • 起動確認後、フォームを作成するための中身を記述していく。
  • まず./src/main.jsの中身を以下にする。
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
  • 次に./src/components/ErrMsg.vueを作成して、中身を以下にする。
<template>
    <span class="err-msg">{{ props.errMsg }}</span>
</template>

<script setup>
const props = defineProps({
    errMsg: String
})
</script>

<style scoped>
.err-msg {
    color: red;
}
</style>
  • 次に./src/App.vueの中身を以下にする。
<template>
  <div>
    <form>
      <div>
        <label>ニックネーム : </label>
        <input
          type="text" id="nickname" v-model.lazy="requireInput.nickname" @blur="valid('nickname')"
        >
        <ErrMsg  v-if="!!errorInput.nickname" :errMsg="errorInput.nickname"/>
      </div>
      <div>
        <label>好きな色 : </label>
        <select
          name="color" id="color" v-model="requireInput.color" @blur="valid('color')"
        >
          <option value="色を選択" selected>色を選択</option>
          <option value="赤">赤</option>
          <option value="青">青</option>
        </select>
        <ErrMsg  v-if="!!errorInput.color" :errMsg="errorInput.color"/>
      </div>
      <div>
        <label>メールアドレス : </label>
        <input
          type="email" id="email" v-model.lazy="requireInput.email" @blur="valid('email')"
        >
        <ErrMsg  v-if="!!errorInput.email" :errMsg="errorInput.email"/>
      </div>
      <div>
        <label>電話番号 : </label>
        <input
          type="tel" id="phone" v-model.lazy="requireInput.phone" @blur="valid('phone')"
        >
        <ErrMsg  v-if="!!errorInput.phone" :errMsg="errorInput.phone"/>
      </div>
      <div>
        <label>OS : </label>
        <input
          type="radio" name="os" value="Windows" v-model="requireInput.os" v-on:change="valid('os')"
        >Windows
        <input
          type="radio" name="os" value="Mac" v-model="requireInput.os" v-on:change="valid('os')"
        >Mac
        <input
          type="radio" name="os" value="Linux" v-model="requireInput.os" v-on:change="valid('os')"
        >Linux
        <ErrMsg  v-if="!!errorInput.os" :errMsg="errorInput.os"/>
      </div>
    </form>
    <div>
      <button @click="sendForm">送信</button>
    </div>
  </div>
</template>

<script setup>
import { reactive } from 'vue';
import { object, string } from 'yup'
import ErrMsg from './components/ErrMsg.vue';

// 必須入力データ
const requireInput = reactive({
  nickname: '',
  color: '色を選択',
  email: '',
  phone: '',
  os: '',
})

// エラーメッセージ
const errorInput = reactive({
  nickname: '',
  color: '',
  email: '',
  phone: '',
  os: '',
})

// 電話番号正規表現
const phoneRegExp = /^((\\+[1-9]{1,4}[ \\-]*)|(\\([0-9]{2,3}\\)[ \\-]*)|([0-9]{2,4})[ \\-]*)*?[0-9]{3,4}?[ \\-]*[0-9]{3,4}?$/;

// 入力スキーマ
const inputSchema = object().shape({
    // 「必須入力・3文字以上」
    nickname: string().trim().min(3,'3文字以上のニックネームを入力してください。'),
    // 「必須選択」
    color: string().matches(/^(?!色)/, { message: '色を選択してください。' }),
    // 「必須入力・メール形式」
    email: string().trim().required('メールアドレスを入力してください。').email('正しい形式のメールアドレスを入力してください。'),
    // 「必須入力・電話番号形式」
    phone: string().trim().required('電話番号を入力してください。').matches(phoneRegExp, {message: '有効な電話番号を入力してください。'}),
    // 「必須選択」
    os: string().oneOf(["Windows", "Mac", "Linux",], 'OSをご選択ください。'),
})

// バリデーション処理
const valid = (field) => {
  inputSchema.validateAt(field, requireInput).then(() => {
    errorInput[field] = ''
  }).catch(err => {
    errorInput[field] = err.message
  })
}

// 送信処理
const sendForm = async () => {
  // ボタン押下時にvalidation
  Object.keys(errorInput).forEach(k => {
    valid(k)
  })
  // errorInput更新
  await new Promise(resolve => setTimeout(resolve, 5))
  // エラーがあれば送信しない。
  if(Object.values(errorInput).some(ei => !!ei)){
    console.log('送信エラー')
    return
  }
  // 送信
  console.log('送信OK')
}
</script>

<style scoped>
</style>
  • 記述後、npm run devで立ち上げたlocalhostで画面に入力フォームが描画されていることを確認します。

動作確認

  • 表示確認後、F12でコンソール画面を開きながら以下の動作等を入力フォームで行い、結果を確認します。
    • 全て未入力状態で「送信」ボタン押下
    • 各入力ボックスで正しい形式と誤った形式混在で入力
    • 全て正しい形式で入力して「送信」ボタン押下
  • 全て正しい形式で入力された状態で押下」の時に、「エラーメッセージなし。コンソールで送信OK」であることを確認します。

  • 以上です。

まとめ

  • 少し冗長になりますが、yupを使えばルールを構造的に記述できて便利な関数も利用できることが分かりました。
  • まだ利用して日が浅いため、今後も色々調査及び検証してどんどん活用していきます。

参考

アノテーション株式会社について

アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。「らしく働く、らしく生きる」のスローガンを掲げ、様々な背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。少しでもご興味あれば、アノテーション株式会社WEBサイトをご覧ください。