【Vue.js】バリデーションライブラリvee-validateの導入と使い方|フォームの信頼性を高める

【Vue.js】バリデーションライブラリvee-validateの導入と使い方|フォームの信頼性を高める Vue.js

フォームの入力チェックを、自前で書くと、項目が増えるたびにコードが膨らみます。vee-validateは、Vue向けのフォームのバリデーションライブラリで、値、エラー、検証のタイミングをまとめて管理できます。この記事では、Vue 3(<script setup>)を前提に、導入から基本の使い方、独自のルール、日本語化までを整理します。自前で検証する場合の考え方は、入力フォームのバリデーション機能を実装する方法をご覧ください。

スポンサーリンク

導入

vee-validate本体に加え、スキーマで検証ルールを書くために、Yup(または Zod)と、そのアダプターを入れます。アダプターの@vee-validate/yupを入れ忘れやすいので注意してください。

Shell:インストール
# vee-validate 本体、Yup との連携用アダプター、Yup
npm i vee-validate @vee-validate/yup yup

2つの使い方

公式は、vee-validateの使い方を2つ案内しています。混ぜて使うこともできます。

  • Composition API:useForm、defineFieldを使います。既存のUIの部品と組み合わせやすい方法です。
  • コンポーネント:<Form>、<Field>、<ErrorMessage>を、テンプレートの中で使います。

基本:useForm と defineField

まず、書いてしまいがちな、動かない例です。

Vue:動かない例
<script setup>
import { useForm } from 'vee-validate'

const { handleSubmit, errors, values } = useForm({ validationSchema: schema })

// NG①:values.name は、ref ではなく、ただの値(最初は undefined)。
//       v-model="name" は、const にバインドすることになり、動かない
const name = values.name

// NG②:handleSubmit(onSubmit) は、送信ハンドラとなる関数を「返す」だけで、呼ばない
// <form @submit="handleSubmit(onSubmit)">
</script>
  • values.nameは、refではなく、ただの値です。v-modelをconstにバインドしても、動きません。入力欄との結びつけには、defineField('name')を使います。
  • handleSubmit(onSubmit)は、検証に通ったときにコールバックを呼ぶ送信ハンドラとなる関数を返すだけです。@submit="handleSubmit(onSubmit)"と書いても、返された関数が呼ばれず、何も起きません。const onSubmit = handleSubmit(...)として受け取り、@submitに渡します。

正しい書き方です。スキーマは、@vee-validate/yupのtoTypedSchema()で包んで渡します。公式の入門ページがこの書き方で、フォームの値に型が付き、必須項目の値がnull許容にならないためです。defineFieldは、[値のモデル, 属性]を返します。

Vue:useFormとdefineFieldとYup
<script setup>
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/yup'
import * as yup from 'yup'

// toTypedSchema で包んで渡す(型が付き、必須項目の値が null 許容にならない)
// label は、エラーメッセージの ${path} に使われる項目名
const schema = toTypedSchema(
  yup.object({
    name: yup.string().label('名前').required(),
    email: yup.string().label('メールアドレス').required().email()
  })
)

const { handleSubmit, errors, defineField } = useForm({
  validationSchema: schema
})

// defineField は [値のモデル, 属性] を返す
const [name, nameAttrs] = defineField('name')
const [email, emailAttrs] = defineField('email')

// handleSubmit は、検証に通ったときだけコールバックを呼ぶ、送信ハンドラを返す
const onSubmit = handleSubmit((values) => {
  console.log('送信データ:', values)
})
</script>

<template>
  <!-- novalidate:ブラウザ標準の検証を止め、エラー表示を vee-validate に統一する -->
  <form novalidate @submit.prevent="onSubmit">
    <div>
      <label for="name">名前</label>
      <input
        id="name"
        v-model="name"
        v-bind="nameAttrs"
        type="text"
        autocomplete="name"
        :aria-invalid="!!errors.name"
        aria-describedby="name-error"
      >
      <p id="name-error" role="alert">{{ errors.name }}</p>
    </div>

    <div>
      <label for="email">メールアドレス</label>
      <input
        id="email"
        v-model="email"
        v-bind="emailAttrs"
        type="email"
        autocomplete="email"
        :aria-invalid="!!errors.email"
        aria-describedby="email-error"
      >
      <p id="email-error" role="alert">{{ errors.email }}</p>
    </div>

    <!-- 送信ボタンは無効にしない。押したときに、エラーを示す -->
    <button type="submit">送信</button>
  </form>
</template>

送信ボタンは、エラーがある間、無効にしない設計にしています。押せない理由が分からなくなるためです。ラベル、aria-invalid、aria-describedby、role="alert"の意味は、入力フォームのバリデーション機能を実装する方法で解説しています。

コンポーネントで書く:Form・Field・ErrorMessage

テンプレートの中で完結させたい場合は、コンポーネントが便利です。<Form>に:validation-schemaを渡し、各<Field>にnameを付け、対応する<ErrorMessage>を置きます。検証に通ったときだけ、@submitのハンドラに値が渡されます。

Vue:コンポーネントによる書き方
<script setup>
import { Form, Field, ErrorMessage } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/yup'
import * as yup from 'yup'

const schema = toTypedSchema(
  yup.object({
    email: yup.string().label('メールアドレス').required().email(),
    password: yup.string().label('パスワード').required().min(8)
  })
)

// 検証に通ったときだけ、値が渡される
function onSubmit(values) {
  console.log('送信データ:', values)
}
</script>

<template>
  <Form :validation-schema="schema" @submit="onSubmit">
    <label for="email">メールアドレス</label>
    <Field id="email" name="email" type="email" />
    <ErrorMessage name="email" />

    <label for="password">パスワード</label>
    <Field id="password" name="password" type="password" />
    <ErrorMessage name="password" />

    <button type="submit">送信</button>
  </Form>
</template>

Yupの代わりに、Zodも使えます。

JavaScript:Zodを使う場合
import { toTypedSchema } from '@vee-validate/zod'
import { z } from 'zod'

// Yup の代わりに Zod も使える(npm i @vee-validate/zod zod)
const schema = toTypedSchema(
  z.object({
    email: z.string().min(1, '必須です').email('正しいメール形式で入力してください')
  })
)

独自のルールを書く

Yupの組み込みのルール(required、min、matches、email)で足りないときは、.test()で独自のルールを書きます。パスワードの確認は、oneOfにyup.refを渡す書き方が定番です。

JavaScript:独自のルールとパスワード確認
import * as yup from 'yup'

const schema = yup.object({
  password: yup
    .string()
    .label('パスワード')
    .required()
    .min(8)
    .matches(/[A-Z]/, '${path}に大文字を含めてください'),

  // パスワードの確認:password と同じ値でなければ、エラー
  passwordConfirm: yup
    .string()
    .label('確認用パスワード')
    .required()
    .oneOf([yup.ref('password')], 'パスワードが一致しません'),

  // 独自のルール:test(名前, メッセージ, 検証する関数)。真を返せば、OK
  username: yup
    .string()
    .label('ユーザー名')
    .test('no-spaces', '空白は使えません', (value) => !value || !/\s/.test(value))
})

なお、Yupのrequired()は、空文字も「未入力」として扱います。email()は、HTMLの仕様と同じ正規表現で判定しますが、公式は、すべての有効なアドレスを網羅できるわけではなく、確実に確かめる方法は確認メールの送信だけ、と注意しています。

エラーメッセージを日本語にする

Yupの既定のメッセージは英語です。項目ごとにメッセージを書く代わりに、setLocaleで、共通のメッセージをまとめて設定できます。スキーマを作る前に、1度だけ呼びます。項目名は、スキーマ側の.label()で指定すると、${path}に入ります。

JavaScript:Yupのメッセージを日本語にする
// main.js など、アプリの起動時に、1度だけ設定する(スキーマを作る前に)
import { setLocale } from 'yup'

setLocale({
  mixed: {
    required: '${path}は必須です'
  },
  string: {
    min: '${path}は${min}文字以上で入力してください',
    email: '${path}の形式が正しくありません'
  }
})

// 項目名は、スキーマ側で .label('名前') と指定すると、${path} に入る

関連記事

よくある質問(FAQ)

QhandleSubmitを使ったのに、送信しても何も起きません。
AhandleSubmit(コールバック)は、送信ハンドラとなる関数を返します。const onSubmit = handleSubmit(...)と受け取り、@submit="onSubmit"のように渡してください。テンプレートに@submit="handleSubmit(onSubmit)"と書いても、返された関数が呼ばれません。
QYupのスキーマは、そのままuseFormに渡せませんか?
A渡せますが、公式は@vee-validate/yupのtoTypedSchema()で包む書き方を示しています。型が付き、必須の項目の値がnull許容にならないためです。
Qエラーメッセージを日本語にするには?
AYupのsetLocaleで、共通のメッセージを設定します。項目名は、スキーマの.label('名前')で指定すると、メッセージの${path}に入ります。
Qエラーがあるとき、送信ボタンを無効にしたほうがよいですか?
Aおすすめしません。押せない理由が分からなくなります。ボタンは押せるままにして、送信時にエラーを示してください。

まとめ

vee-validateは、useFormとdefineField、または<Form>と<Field>で、値、エラー、検証のタイミングを一元管理できます。YupのスキーマはtoTypedSchema()で包み、handleSubmitは返された関数を@submitに渡します。独自のルールは.test()、日本語化はsetLocaleで行い、送信ボタンは無効にしない設計にしてください。