vee-validateをvue3で使ってみる

vue.js
vee-validateをvue3で使ってみる

vee-validate

参考サイト

install

npm i vee-validate@next --save

フィールドレベルのバリデーション

検証フィールドごとにバリデーションする

Field, Form, ErrorMessage コンポネントをインポート

  • Field:検証するフィールドのコンポネント
  • Form: フォームのコンポネント
  • ErrorMessage: エラーメッセージを出すコンポネント

name フィールドを作成
type 属性をテキスト(デフォルト)
isRequired ルールを methods で作成
Field コンポネントの `rules に登録

<template>
  <Form>
    <div>
      <h2 class="header1">name</h2>
      <Field name="field" :rules="isRequired" />
      <div><ErrorMessage name="field" /></div>
    </div>
  </Form>
</template>

<script>
import { Field, Form, ErrorMessage } from 'vee-validate';
export default {
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  methods: {
    isRequired(value) {
      if (value && value.trim()) {
        return true;
      }
      return 'This is required';
    },
  },
};
</script>

フォームレベルのバリデーション

フォームで各フィールドをまとめてバリデーションする

data でバリデーションスキーマを作成
Form コンポネントの validation-schema に登録
フィールド名をコンテキストから取得

<template>
  <Form :validation-schema="validation_schema">
    <div>
      <h2 class="header1">name</h2>
      <Field name="name" type="text" />
      <div><ErrorMessage name="name" /></div>
    <div>
  </Form>
</template>
<script>
// Field, Form, ErrorMessage コンポネントをインポート
import { Field, Form, ErrorMessage } from 'vee-validate';
export default {
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  data() {
    const validation_schema = {
      name(value) {
        // trueの場合
        if (value && value.trim()) {
          return true;
        }
        // falseの場合
        return "This field is required";
      },
      email(value) {
        // validate email value...
      },
      password(value) {
        // validate password value...
      },
    };

    return {
      simpleSchema,
    };
  },
};
</script>

yup バリデータを使用する

javascriptのバリデーションライブラリとして有名なyupを使用する

npm i yup --save

yupをインポート
'email'フィールドに string スキーマの「必須」と「メールアドレス型」を検証
'password'フィールドに string スキーマの「必須」と「8文字以上」を検証
メッセージはファンクションに引数として与える

<template>
  <Form :validation-schema="validation_schema">
      <div>
        <h2 class="header1">email</h2>
        <Field name="email" type="email" />
        <div><ErrorMessage name="email" /></div>
      </div>

      <div>
        <h2 class="header1">password</h2>
        <Field name="password" type="password" />
        <div><ErrorMessage name="password" /></div>
      </div>
  </Form>
</template>
<script>
// Field, Form, ErrorMessage コンポネントをインポート
import { Field, Form, ErrorMessage } from 'vee-validate';
// yup をインポート
import * as yup from "yup";

export default {
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  data() {
    const validation_schema = {
        email: yup.string().required('email is required!').email('email is required!'),
        password: yup
        .string()
        .required("password is required!")
        .min(8, `password must be at least 8 characters!`),
    };
  },
};
</script>

バリデーションするイベントの変更

全部ONにする

<script>
// configureを追加でインポート
import { Form, Field, ErrorMessage, configure } from "vee-validate";

configure({
  validateOnBlur: true,   // `blur`イベントで検証をトリガー
  validateOnChange: true, // `change`イベントで検証をトリガー
  validateOnInput: true,  // `input`イベントで検証をトリガー
  validateOnModelUpdate: true, // update:modelValueイベントで検証をトリガー
});
</script>

エラーとバリューとメタの取得

スコープ付きスロットに設定してある values 、 errors 、 meta を呼び出して使用する

  • values: 入力した値のオブジェクト
  • errors: バリデーションエラーのオブジェクト
  • meta.initialValues: フィールドの初期値、リセットするとこの値に戻る
  • meta.touched: フィールドにふれたらtrue
  • meta.valid: すべてのフィールドが有効ならtrue
  • meta.dirty: フィールドの値に変更があればtrue

Form コンポネントの v-slot に initial-values と initial-errors を追加する
値を data で設定する

  • initial-values: フィールドの初期値を設定する
  • initial-errors: エラーの初期値を設定する
<template>
  <!-- スロットの呼び出し -->
  <Form
      v-slot="{
        values,
        errors,
        meta,
      }"
      :validation-schema="validation_schema"
      :initial-values="initial_values"
      :initial-errors="initial_errors"
  >
    <div>
      <h2 class="header1">name</h2>
      <Field name="name" type="text" />
      <div><ErrorMessage name="name" /></div>
    <div>

    <div>
      <h2 class="header1">email</h2>
      <Field name="email" type="email" />
      <div><ErrorMessage name="email" /></div>
    </div>

    <div>
      <h2 class="header1">password</h2>
      <Field name="password" type="password" />
      <div><ErrorMessage name="password" /><div>
    </div>

    <div>
      <!-- valuesを表示 -->
      <h2>values</h2>
      <pre>{{ values }}</pre>
    </div>

    <div>
      <!-- errorsを表示 -->
      <h2>errors</h2>
      <pre>{{ errors }}</pre>
    </div>

    <div>
      <!-- meta.initialValuesを表示 -->
      <h2>meta.initialValues</h2>
      <pre>{{ meta.initialValues }}</pre>
    </div>

    <div>
      <!-- meta.touchedを表示 -->
      <h2>meta.touched</h2>
      <span>{{ meta.touched }}</span>
    </div>

    <div>
      <!-- meta.validを表示 -->
      <h2>meta.valid</h2>
      <span>{{ meta.valid }}</span>
    </div>

    <div>
      <!-- meta.dirtyを表示 -->
      <h2>meta.dirty</h2>
      <span>{{ meta.dirty }}</span>
    </div>
  </Form>
</template>
<script>
// インポート
import { Form, Field, ErrorMessage, configure } from "vee-validate";
// yup をインポート
import * as yup from "yup";

configure({
  validateOnBlur: true,   // `blur`イベントで検証をトリガー
  validateOnChange: true, // `change`イベントで検証をトリガー
  validateOnInput: true,  // `input`イベントで検証をトリガー
  validateOnModelUpdate: true, // update:modelValueイベントで検証をトリガー
});

export default {
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  data() {
    const validation_schema = {
        name(value) {
          // trueの場合
          if (value && value.trim()) {
            return true;
          }
          // falseの場合
          return "This field is required";
        },
        email: yup.string().required('email is required!').email('email is required!'),
        password: yup
        .string()
        .required("password is required!")
        .min(8, `password must be at least 8 characters!`),
    };

    return {
      validation_schema,
    };
  },
};
</script>

サブミットとリセット

submit ボタンを追加する
Form コンポネント内なのですべて有効にならないとサブミットしない
Form コンポネント内なのでデフォルトではリロードしない
methods に onSubmit を作成
Form コンポネントの submit イベントに onSubmit を登録
meta.valid が true になるまで disable にする
送信中は isSubmitting を使って disable にする
submitCount を使って送信回数を表示する

reset ボタンを追加する
Form コンポネント内なので meta.initialValues に設定してある値にリセットされる
meta.dirty がtrueになるまで disable にする-->

<template>
  <!-- submitイベントにメソッドを追加 -->
  <Form
    v-slot="{
      values,
      errors,
      meta,
      isSubmitting,
      submitCount
    }"
    :validation-schema="validation_schema"
    :initial-values="initial_values"
    :initial-errors="initial_errors"
    @submit="onSubmit"
  >
    <div>
      <h2 class="header1">name</h2>
      <Field name="name" type="text" />
      <div><ErrorMessage name="name" /></div>
    <div>

    <div>
      <h2 class="header1">email</h2>
      <Field name="email" type="email" />
      <div><ErrorMessage name="email" /></div>
    </div>

    <div>
      <h2 class="header1">password</h2>
      <Field name="password" type="password" />
      <div><ErrorMessage name="password" /><div>
    </div>

    <!--サブミットボタンを追加-->
    <!--meta.validがtrueになるまで`disable`にする-->
    <!--送信中は`isSubmitting`を使って`disable`にする-->
    <button type="submit" :disabled="!meta.valid || isSubmitting">
        send <span>{{ submitCount }}</span>
    </button>

    <!--サブミットボタンを追加-->
    <button type="reset" :disabled="!meta.dirty">Reset</button>

  </Form>
</template>
<script>
// インポート
import { Form, Field, ErrorMessage, configure } from "vee-validate";
// yup をインポート
import * as yup from "yup";

configure({
  validateOnBlur: true,   // `blur`イベントで検証をトリガー
  validateOnChange: true, // `change`イベントで検証をトリガー
  validateOnInput: true,  // `input`イベントで検証をトリガー
  validateOnModelUpdate: true, // update:modelValueイベントで検証をトリガー
});

export default {
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  data() {
    // validation schema
    const validation_schema = {
      name(value) {
        // trueの場合
        if (value && value.trim()) {
          return true;
        }
        // falseの場合
        return "This field is required";
      },
      email: yup.string().required('email is required!').emai  ('email is required!'),
      password: yup
        .string()
        .required("password is required!")
        .min(8, `password must be at least 8 characters!`),
    };
    // initial values
    const initial_values = {
      name: "user",
      email: "user@example.com",
      password: "useruser",
    };
    // initial errors
    const initial_values = {
      name: "This name is already taken",
      email: "This email is already taken",
      password: "The password is too short",
    };
    return {
      validation_schema,
      initial_values,
      initial_errors,
    };
  },
  methods: {
    // 3秒後にレスポンス
    async onSubmit(values, { resetForm }) {
      await new Promise((resolve) => setTimeout  (resolve, 3000));
      console.log(values);
      resetForm();
     },
  },
};
</script>

カスタムバリデーションとフィールド名の変更

defineRule を追加でインポート
defineRule で新しいバリデーションを作成
Field コンポネントの label でフィールド名を変更

<template>
  <Form
    v-slot="{
      meta,
      isSubmitting,
    }"
    :validation-schema="validation_schema"
    @submit="onSubmit"
  >
    <div>
      <h2 class="header1">password</h2>
      <Field name="password" type="password" />
      <!-- confirmationのフィールドを追加 -->
      <Field name="confirmation" type="password" label="☆confirmation☆" />
      <div><ErrorMessage name="password" /></div>
      <!-- confirmationのエラーメッセージを追加 -->
      <div><ErrorMessage name="confirmation" /></div>
    </div>

    <div>
      <h2 class="header1">message</h2>
      <!-- messageのフィールドを追加 -->
      <Field name="message" type="text" label="☆message☆" />
      <!-- messageのエラーメッセージを追加 -->
      <div><ErrorMessage name="message" /></div>
    </div>

    <div>
      <h2 class="header1">age</h2>
      <!-- ageのフィールドを追加 -->
      <Field name="age" type="number" label="☆age☆" />
      <!-- ageのエラーメッセージを追加 -->
      <div><ErrorMessage name="age" /></div>
    </div>

    <button type="submit" :disabled="!meta.valid || isSubmitting">
        send <span>{{ submitCount }}</span>
    </button>

    <button type="reset" :disabled="!meta.dirty">Reset</button>

  </Form>
</template>
<script>
import { Form, Field, ErrorMessage, defineRule } from "vee-validate";
import * as yup from "yup";

// 「必須」を追加
defineRule("required", (value, [], context) => {
  if (!value || !value.length) {
    return `${context.field} is required!`;
  }
  return true;
});

// 「最小文字数」を追加
defineRule("minLength", (value, [limit], context) => {
  // The field is empty so it should pass
  if (!value || !value.length) {
    return true;
  }
  if (value.length < limit) {
    return `${context.field} must be at least ${limit} characters`;
  }
  return true;
});

// 「最小数、最大数」を追加
defineRule("minmax", (value, [min, max], context) => {
  // The field is empty so it should pass
  if (!value || !value.length) {
    return true;
  }
  const numericValue = Number(value);
  if (numericValue < min) {
    return `${context.field} must be greater than ${min}`;
  }
  if (numericValue > max) {
    return `${context.field} must be less than ${max}`;
  }
  return true;
});

// 「コンファーム」を追加
defineRule("confirmed", (value, [target], context) => {
  if (value === target) {
    return true;
  }
  return `${context.field} must match`;
});

// 「コンファーム」を追加
defineRule("confirmed", (value, [target]) => {
  if (value === target) {
    return true;
  }
  return "Passwords must match";
});

export default {
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  data() {
    const validation_schema = {
      password: yup
        .string()
        .required("password is required!")
        .min(8, `password must be at least 8 characters!`),
      // confirmationに`required`と`confirmed`を追加
      // 参照するフィールドは@をつけて指定
      confirmation: "required|confirmed:@password",
      // オブジェクトでも書ける
      message: {required: true, minLength: 5},
      age: {required: true, minmax: [5, 10]},
    };
    return {
      validation_schema,
    };
  },
  methods: {
    // 3秒後にレスポンス
    async onSubmit(values, { resetForm }) {
      await new Promise((resolve) => setTimeout  (resolve, 3000));
      console.log(values);
      resetForm();
     },
  },
};
</script>

セレクト、ラジオボタン、チェックボックス

<template>
  <Form
    v-slot="{
      meta,
      isSubmitting
    }"
    :validation-schema="validation_schema"
    @submit="onSubmit"
  >

    <div>
      <h2 class="header1">セレクト</h2>
      <Field name="item" as="select">
        <option value="">---</option>
        <option value="coffee">Coffee</option>
        <option value="tea">Tea</option>
        <option value="coke">Coke</option>
      </Field>
      <div><ErrorMessage name="item" /></div>
    </div>

    <div>
      <h2 class="header1">ラジオボタン</h2>
      <label><Field name="gender" type="radio" value="male" />male</label>
      <label><Field name="gender" type="radio" value="female" />female</label>
      <label><Field name="gender" type="radio" value="other" />other</label>
      <div><ErrorMessage name="gender" /></div>
    </div>

    <div>
      <h2 class="header1">チェックボックス</h2>
      <label><Field name="categories" type="checkbox" value="111" />category1</label>
      <label><Field name="categories" type="checkbox" value="222" />category2</label>
      <label><Field name="categories" type="checkbox" value="33333" />category3</label>
      <div><ErrorMessage name="categories" /></div>
    </div>
      
    <button type="submit" :disabled="!meta.valid || isSubmitting">send</button>

    <button type="reset" :disabled="!meta.dirty">Reset</button>

  </Form>
</template>
<script>
import { Form, Field, ErrorMessage } from "vee-validate";
import * as yup from "yup";

export default {
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  data() {
    const validation_schema = {
      item: yup
        .mixed()
        .label("item")
        .oneOf(
          ["coffee", "tea"],
          "${path} must be one of the following values: ${values} !"
        ),
      gender: yup
        .mixed()
        .label("gender")
        .oneOf(
          ["male", "female", "other"],
          "${path} must be one of the following values: ${values} !"
        ),
      categories: yup
        .array()
        .label("categories")
        .length(2, "${path} must have ${length} items!")
        .of(yup.string().max(3, "category must be at most ${max} characters!")),
    };
    return {
      validation_schema,
    };
  },
  methods: {
    // 3秒後にレスポンス
    async onSubmit(values, { resetForm }) {
      await new Promise((resolve) => setTimeout  (resolve, 3000));
      console.log(values);
      resetForm();
     },
  },
};
</script>

配列, オブジェクト

<template>
  <Form
    v-slot="{
      meta,
      isSubmitting,
    }"
    :validation-schema="validation_schema"
    @submit="onSubmit"
  >
    <div>
      <h2 class="header1">urls 配列</h2>
      <Field name="urls[0]" type="url" />
      <Field name="urls[1]" type="url" />
      <div><ErrorMessage name="urls" /></div>
    </div>

    <div>
      <h2 class="header1">sites オブジェクト</h2>
      <Field name="sites.twitter" type="url" />
      <Field name="sites.github" type="url" />
      <div><ErrorMessage name="sites" /></div>
    </div>

    <button type="submit" :disabled="!meta.valid || isSubmitting">send</button>

    <button type="reset" :disabled="!meta.dirty">Reset</button>

  </Form>
</template>
<script>
import { Form, Field, ErrorMessage } from "vee-validate";
import * as yup from "yup";

export default {
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  data() {
    const validation_schema = {
      urls: yup
        .array()
        .label("urls")
        .of(yup.string().label("url").url("${path} must be a valid URL")),
      sites: yup.object().shape({
        twitter: yup.string().label("twitter").url("${path} must be a valid URL"),
        github: yup.string().label("github").url("${path} must be a valid URL"),
      }),
    };
    // initial values
    const initial_values = {
      urls: ["https://github.com/asdf"],
      sites: {
        github: "https://github.com/asdf",
      },
      "links.twitter": "https://github.com/asdf",
    };
    return {
      validation_schema,
      initial_values,
    };
  },
  methods: {
    // 3秒後にレスポンス
    async onSubmit(values, { resetForm }) {
      await new Promise((resolve) => setTimeout (resolve, 3000));
      console.log(values);
      resetForm();
     },
  },
};
</script>

繰り返しのフィールド

FieldArray を使用して増減可能なフィールドの作成
FieldArray の fields アイテムを v-for に使用する
FieldArray の fields の一つ一つを entry とする
v-for の key プロパティには entry.key を使用、このプロパティはユニークなので反復キーとして使用することができる
push メソッドで配列のフィールドを追加
remove メソッドでインデックスを指定して削除

<template>
  <Form
    v-slot="{
      meta,
      isSubmitting,
    }"
    :validation-schema="validation_schema"
    :initial-values="initial_values"
    @submit="onSubmit"
  >

      <div>
        <h2 class="header1">products 繰り返しフィールド</h2>
        <FieldArray name="products" v-slot="{ fields, push, remove }">
          <div v-for="(entry, idx) in fields" :key="entry.key">
            <Field :name="`products[${idx}]`" type="text" />
            <button type="button" @click="remove(idx)">Remove</button>
          </div>
          <button type="button" @click="push()">Add</button>
        </FieldArray>
        <div class="validation-notice"><ErrorMessage name="products" /></div>
      </div>

      <div>
        <h2 class="header1">users 繰り返しフィールド array in object</h2>
        <FieldArray name="users" v-slot="{ fields, push, remove }">
          <div v-for="(entry, idx) in fields" :key="entry.key">
            <Field :name="`users[${idx}].name`" type="text" />
            <Field :name="`users[${idx}].age`" type="number" />
            <label
              ><Field :name="`users[${idx}].gender`" type="radio" value="male" />male
            </label>
            <label>
              <Field :name="`users[${idx}].gender`" type="radio" value="female" />female
            </label>
            <label>
              <Field :name="`users[${idx}].gender`" type="radio" value="other" />other
            </label>
            <button type="button" @click="remove(idx)">Remove</button>
          </div>
          <button type="button" @click="push({ name: '', age: 1, gender: '' })">
            Add
          </button>
        </FieldArray>
        <div class="validation-notice"><ErrorMessage name="users" /></div>
      </div>

    <button type="submit" :disabled="!meta.valid || isSubmitting">send</span>
    </button>

    <button type="reset" :disabled="!meta.dirty">Reset</button>

  </Form>
</template>
<script>
import { Form, Field, ErrorMessage, FieldArray } from "vee-validate";
import * as yup from "yup";

export default {
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  data() {
    const validation_schema = {

    };
    // initial values
    const initial_values = {
      products: yup
        .array()
        .label("products")
        .max(3, "${path} field must have less than or equal to ${max} items!"),
      users: yup
        .array()
        .label("users")
        .max(5, "${path} field must have less than or equal to ${max} items!")
        .of(
          yup.object().shape({
            name: yup.string().label("name").required("${path} is a required field!"),
            age: yup
              .number()
              .label("age")
              .typeError("${path} must be a number!")
              .required("${path} is a required field!!")
              .positive()
              .integer()
              .max(5, "${path} is Very big!"),
            gender: yup.string().label("gender").required("${path} is a required field!"),
          })
        ),
    };
    // initial values
    const initial_values = {
      name: "user",
      email: "user@example.com",
      password: "useruser",
      urls: ["https://github.com/asdf"],
      sites: {
        github: "https://github.com/asdf",
      },
      products: [null],
      users: [{ name: "asdf", age: 1, gender: "male" }],
    };

    return {
      validation_schema,
      initial_values,
    };
  },
  methods: {
    // 3秒後にレスポンス
    async onSubmit(values, { resetForm }) {
      await new Promise((resolve) => setTimeout (resolve, 3000));
      console.log(values);
      resetForm();
     },
  },
};
</script>

laravelと同じバリデーションルールを使用する

npm i @vee-validate/rules

ひとつづつインポート

import {
    defineRule
} from 'vee-validate';
import {
    required,
    email,
    min
} from '@vee-validate/rules';

defineRule('required', required);
defineRule('email', email);
defineRule('min', min);

全部インポート

import {
    defineRule
} from 'vee-validate';
import * as rules from "@vee-validate/rules";
Object.keys(rules).forEach((rule) => {
    if (rule !== "default") {
        defineRule(rule, rules[rule]);
    }
});

多言語化する

npm i @vee-validate/i18n
import {
    configure
} from 'vee-validate';
import {
    localize
} from '@vee-validate/i18n';
import en from '@vee-validate/i18n/dist/locale/en.json';
import ja from '@vee-validate/i18n/dist/locale/ja.json';

configure({
    generateMessage: localize({
        en,
        ja,
    }),
});

// change locale
setLocale('ja');

実装する

<template>
  <Form

    v-slot="{
      meta,
      isSubmitting,
    }"
    :validation-schema="validation_schema"
    @submit="onSubmit"

  >

    <div>
      <h2 class="header1">description</h2>
      <Field name="description" type="text" />
      <div class="validation-notice"><ErrorMessage name="description" /></div>
    </div>

    <div>
      <h2 class="header1">image</h2>
      <Field name="image" type="file" />
      <div class="validation-notice"><ErrorMessage name="image" /></div>
    </div>

    <div>
      <h2 class="header1">photos</h2>
      <Field name="photos" type="file" multiple />
      <div class="validation-notice"><ErrorMessage name="photos" /></div>
    </div>

    <button type="submit" :disabled="!meta.valid || isSubmitting">send</span>
    </button>

    <button type="reset" :disabled="!meta.dirty">Reset</button>

  </Form>
</template>
<script>
import { Form, Field, ErrorMessage, defineRule } from "vee-validate"; 
// import { alpha, alpha_num, one_of, image } from "@vee-validate/rules"; 
import * as rules from "@vee-validate/rules"; 
Object.keys(rules).forEach((rule) => {
  if (rule !== "default") {

    defineRule(rule, rules[rule]);

  }
}); 

export default {
  components: {

    Field,
    Form,
    ErrorMessage,

  }, 
  data() {

    const validation_schema = {
        description: {required: true, alpha: true, min: 1, max: 10, }, 
        image: {required: true, ext: ['jpg', 'png'], size: 100 }, 
        photos: {required: true, ext: ['jpg', 'png'], size: 100 }
    }; 
    return {
      validation_schema,
    };

  }, 
  methods: {

    // 3秒後にレスポンス
    async onSubmit(values, { resetForm }) {
      await new Promise((resolve) => setTimeout (resolve, 3000));
      console.log(values);
      resetForm();
     },

  }, 
};
</script>

その他

onInvalidSubmit

onInvalidSubmit イベントを取得してフィールドの値が有効でない場合のメソッドを設定できる

<!-- ... -->
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit">
    <!-- ... -->
</form>
<!-- ... -->
// ...
methods: {
    async onSubmit(values, {
        resetForm
    }) {
        await new Promise((resolve) => setTimeout(resolve, 3000));
        console.log(values);
        resetForm();
    },
    onInvalidSubmit({
        values,
        errors,
        results
    }) {
        console.log(values); // current form values
        console.log(errors); // a map of field names and their first error message
        console.log(results); // a detailed map of field names and their validation results
    },
},
// ...

setFieldValueとsetValues

setFieldValueでメソッドでフィールドの値を設定、
setValuesでフィールド全体の値を設定

<!-- ... -->
<Form v-slot="{
        setFieldValue,
        setValues}">
    <!-- ... -->
    <button type="button" @click="setFieldValue('name', 'kohei')">
        Set Field Value
    </button>

    <button type="button" @click="
          setValues({
            name: 'user',
            email: 'user@user.user',
            password: 'paspaspaspas',
            item: 'coffee',
          })
        ">
        Set Multiple Values
    </button>
</form>
<!-- ... -->

デモ

<template>
  <div id="app">
    <!-- スロットの呼び出し -->
    <Form
      v-slot="{
        values,
        errors,
        meta,
        isSubmitting,
        submitCount,
        setFieldValue,
        setValues,
      }"
      @submit="onSubmit"
      @invalid-submit="onInvalidSubmit"
      :validation-schema="validation_schema"
      :initial-values="initial_values"
      :initial-errors="initial_errors"
      ref="testForm"
    >
      <button type="button" @click="setFieldValue('name', 'kohei')">
        Set Field Value
      </button>

      <button
        type="button"
        @click="
          setValues({
            name: 'kohei',
            email: 'kohei@gmail.com',
            password: 'paspaspaspas',
            item: 'coffee',
          })
        "
      >
        Set Multiple Values
      </button>

      <div>
        <h2 class="header1">name</h2>
        <Field name="name" type="text" />
        <div class="validation-notice"><ErrorMessage name="name" /></div>
      </div>

      <div>
        <h2 class="header1">email</h2>
        <Field name="email" type="email" />
        <div class="validation-notice"><ErrorMessage name="email" /></div>
      </div>

      <div>
        <h2 class="header1">password</h2>
        <Field name="password" type="password" />
        <Field name="confirmation" type="password" label="☆confirmation☆" />
        <div class="validation-notice"><ErrorMessage name="password" /></div>
        <div class="validation-notice"><ErrorMessage name="confirmation" /></div>
      </div>

      <div>
        <h2 class="header1">message</h2>
        <Field name="message" type="text" label="☆message☆" />
        <div class="validation-notice"><ErrorMessage name="message" /></div>
      </div>

      <div>
        <h2 class="header1">age</h2>
        <Field name="age" type="number" label="☆age☆" />
        <div class="validation-notice"><ErrorMessage name="age" /></div>
      </div>

      <div>
        <h2 class="header1">item セレクト</h2>
        <Field name="item" as="select">
          <option value="">---</option>
          <option value="coffee">Coffee</option>
          <option value="tea">Tea</option>
          <option value="coke">Coke</option>
        </Field>
        <div class="validation-notice"><ErrorMessage name="item" /></div>
      </div>

      <div>
        <h2 class="header1">gender ラジオボタン</h2>
        <label><Field name="gender" type="radio" value="male" />male</label>
        <label><Field name="gender" type="radio" value="female" />female</label>
        <label><Field name="gender" type="radio" value="other" />other</label>
        <div class="validation-notice"><ErrorMessage name="gender" /></div>
      </div>

      <div>
        <h2 class="header1">categories チェックボックス</h2>
        <label><Field name="categories" type="checkbox" value="111" />category1</label>
        <label><Field name="categories" type="checkbox" value="222" />category2</label>
        <label><Field name="categories" type="checkbox" value="33333" />category3</label>
        <div class="validation-notice"><ErrorMessage name="categories" /></div>
      </div>

      <div>
        <h2 class="header1">urls 配列</h2>
        <Field name="urls[0]" type="url" />
        <Field name="urls[1]" type="url" />
        <div class="validation-notice"><ErrorMessage name="urls" /></div>
      </div>

      <div>
        <h2 class="header1">sites オブジェクト</h2>
        <Field name="sites.twitter" type="url" />
        <Field name="sites.github" type="url" />
        <div class="validation-notice"><ErrorMessage name="sites" /></div>
      </div>

      <div>
        <h2 class="header1">products 繰り返しフィールド</h2>
        <FieldArray name="products" v-slot="{ fields, push, remove }">
          <div v-for="(entry, idx) in fields" :key="entry.key">
            <Field :name="`products[${idx}]`" type="text" />
            <button type="button" @click="remove(idx)">Remove</button>
          </div>
          <button type="button" @click="push()">Add</button>
        </FieldArray>
        <div class="validation-notice"><ErrorMessage name="products" /></div>
      </div>

      <div>
        <h2 class="header1">users 繰り返しフィールド array in object</h2>
        <FieldArray name="users" v-slot="{ fields, push, remove }">
          <div v-for="(entry, idx) in fields" :key="entry.key">
            <Field :name="`users[${idx}].name`" type="text" />
            <Field :name="`users[${idx}].age`" type="number" />
            <label
              ><Field :name="`users[${idx}].gender`" type="radio" value="male" />male
            </label>
            <label>
              <Field :name="`users[${idx}].gender`" type="radio" value="female" />female
            </label>
            <label>
              <Field :name="`users[${idx}].gender`" type="radio" value="other" />other
            </label>
            <button type="button" @click="remove(idx)">Remove</button>
          </div>
          <button type="button" @click="push({ name: '', age: 1, gender: '' })">
            Add
          </button>
        </FieldArray>
        <div class="validation-notice"><ErrorMessage name="users" /></div>
      </div>

      <div>
        <h2 class="header1">description</h2>
        <Field name="description" type="text" />
        <div class="validation-notice"><ErrorMessage name="description" /></div>
      </div>

      <div>
        <h2 class="header1">image</h2>
        <Field name="image" type="file" />
        <div class="validation-notice"><ErrorMessage name="image" /></div>
      </div>

      <div>
        <h2 class="header1">photos</h2>
        <Field name="photos" type="file" multiple />
        <div class="validation-notice"><ErrorMessage name="photos" /></div>
      </div>

      <button type="submit" :disabled="!meta.valid || isSubmitting">
        send <span>{{ submitCount }}</span>
      </button>

      <button type="reset" :disabled="!meta.dirty">Reset</button>

      <div>
        <h2>values</h2>
        <pre>{{ values }}</pre>
      </div>

      <div>
        <h2>errors</h2>
        <pre>{{ errors }}</pre>
      </div>

      <div>
        <h2>meta.initialValues</h2>
        <pre>{{ meta.initialValues }}</pre>
      </div>

      <div>
        <h2>meta.touched</h2>
        <span>{{ meta.touched }}</span>
      </div>

      <div>
        <h2>meta.valid</h2>
        <span>{{ meta.valid }}</span>
      </div>

      <div>
        <h2>meta.dirty</h2>
        <span>{{ meta.dirty }}</span>
      </div>
    </Form>
  </div>
</template>

<script>
import {
  Form,
  Field,
  ErrorMessage,
  FieldArray,
  configure,
  defineRule,
} from "vee-validate";
import * as yup from "yup";
import { localize, setLocale } from "@vee-validate/i18n";
import en from "@vee-validate/i18n/dist/locale/en.json";
import ja from "@vee-validate/i18n/dist/locale/ja.json";

// import { alpha, alpha_num, one_of, image } from "@vee-validate/rules";
import * as rules from "@vee-validate/rules";
Object.keys(rules).forEach((rule) => {
  if (rule !== "default") {
    defineRule(rule, rules[rule]);
  }
});

configure({
  validateOnBlur: true, // controls if `blur` events should trigger validation with `handleChange` handler
  validateOnChange: true, // controls if `change` events should trigger validation with `handleChange` handler
  validateOnInput: true, // controls if `input` events should trigger validation with `handleChange` handler
  validateOnModelUpdate: true, // controls if `update:modelValue` events should trigger validation with `handleChange` handler
  generateMessage: localize({
    // en: {
    //   messages: {
    //     alpha: "alpha only!",
    //   },
    // },
    // ja: {
    //   messages: {
    //     alpha: "アルファベットだけ!",
    //   },
    // },
    en,
    ja,
  }),
});

// 「必須」を追加
// defineRule("required", (value, [], context) => {
//   if (!value || !value.length) {
//     return `${context.field} is required!`;
//   }
//   return true;
// });

// 「最小文字数」を追加
defineRule("minLength", (value, [limit], context) => {
  // The field is empty so it should pass
  if (!value || !value.length) {
    return true;
  }
  if (value.length < limit) {
    return `${context.field} must be at least ${limit} characters`;
  }
  return true;
});

// 「最小数、最大数」を追加
defineRule("minmax", (value, [min, max], context) => {
  // The field is empty so it should pass
  if (!value || !value.length) {
    return true;
  }
  const numericValue = Number(value);
  if (numericValue < min) {
    return `${context.field} must be greater than ${min}`;
  }
  if (numericValue > max) {
    return `${context.field} must be less than ${max}`;
  }
  return true;
});

// 「コンファーム」を追加
defineRule("confirmed", (value, [target], context) => {
  if (value === target) {
    return true;
  }
  return `${context.field} must match`;
});

export default {
  components: {
    Form,
    Field,
    ErrorMessage,
    FieldArray,
  },
  data() {
    // validation schema
    const validation_schema = {
      name(value, context) {
        if (value && value.trim()) {
          return true;
        }
        return "This field is required.";
      },
      email: yup
        .string()
        .label("email")
        .required("${path} is required!")
        .email("${path} must be a valid email!"),
      password: yup
        .string()
        .label("password")
        .required("${path} is required!")
        .min(8, "${path} must be at least ${min} characters!"),
      // confirmationに`required`と`confirmed`を追加
      confirmation: "required|confirmed:@password",
      // オブジェクトでも書ける
      message: { required: true, minLength: 5 },
      age: { required: true, minmax: [5, 10] },
      // messageは「``」ではなくてダブルコーテーション「""」、
      item: yup
        .mixed()
        .label("item")
        .oneOf(
          ["coffee", "tea"],
          "${path} must be one of the following values: ${values} !"
        ),
      gender: yup
        .mixed()
        .label("gender")
        .oneOf(
          ["male", "female", "other"],
          "${path} must be one of the following values: ${values} !"
        ),
      categories: yup
        .array()
        .label("categories")
        .length(2, "${path} must have ${length} items!")
        .of(
          yup
            .string()
            .label("category")
            .max(3, "${path} must be at most ${max} characters!")
        ),
      urls: yup
        .array()
        .label("urls")
        .of(yup.string().label("url").url("${path} must be a valid URL!")),
      sites: yup.object().shape({
        twitter: yup.string().label("twitter").url("${path} must be a valid URL!"),
        github: yup.string().label("github").url("${path} must be a valid URL!"),
      }),
      products: yup
        .array()
        .label("products")
        .max(3, "${path} field must have less than or equal to ${max} items!"),
      users: yup
        .array()
        .label("users")
        .max(5, "${path} field must have less than or equal to ${max} items!")
        .of(
          yup.object().shape({
            name: yup.string().label("name").required("${path} is a required field!"),
            age: yup
              .number()
              .label("age")
              .typeError("${path} must be a number!")
              .required("${path} is a required field!!")
              .positive()
              .integer()
              .max(5, "${path} is Very big!"),
            gender: yup.string().label("gender").required("${path} is a required field!"),
          })
        ),
      description: { required: true, alpha: true, min: 1, max: 10 },
      image: { required: true, ext: ["jpg", "png"], size: 100 },
      photos: { required: true, ext: ["jpg", "png"], size: 100 },
    };
    // initial values
    const initial_values = {
      name: "user",
      email: "user@example.com",
      password: "useruser",
      urls: ["https://github.com/asdf"],
      sites: {
        github: "https://github.com/asdf",
      },
      products: [null],
      users: [{ name: "asdf", age: 1, gender: "male" }],
    };
    // initial errors
    const initial_errors = {
      name: "This name is already taken!!",
      email: "This email is already taken!!",
      password: "The password is too short!!",
    };
    return {
      validation_schema,
      initial_values,
      initial_errors,
    };
  },
  methods: {
    async onSubmit(values, { resetForm }) {
      await new Promise((resolve) => setTimeout(resolve, 3000));
      console.log(values);
      resetForm();
    },
    onInvalidSubmit({ values, errors, results }) {
      console.log(values); // current form values
      console.log(errors); // a map of field names and their first error message
      console.log(results); // a detailed map of field names and their validation results
    },
  },
};
</script>

<style scoped>
.validation-notice {
  color: var(--color-caution);
}
</style>

この記事をシェアする