LoginSignup
19
10

More than 5 years have passed since last update.

VeeValidate を custom component + vuex + custom rule で使う

Last updated at Posted at 2018-04-29

はじめに

Vue.js で作っているプロジェクトでフォームを作る場合には、バリデーションの実装を VeeValidate を用いることが多いです。

非常に便利なプラグインなのですが、デフォルト設定のまま利用するとフォーム1つに対してのバリデーションチェックのみしか出来なかったり、Atomic Design でフォームの input, select 要素を custom component にした場合に動作しなかったりと、
実運用でハマりポイントがいくつかあるので段階をおって説明したいと思います。

作るもの

  • 注文入力欄
    • 入力必須
    • 最大文字数: 10文字

Level 1

デフォルトルールで特にコンポーネントを分ける必要が無い場合。

画面側

Order.vue
<template>
  <input
    v-validate="{ required:true, max:10 }"
    :class="{ 'error': errors.has('order') }"
    name="order"
    :placeholder="上ハラミ"
    type="text"
  >
</template>

(焼肉食いたいですね)

これはドキュメントを見ればすぐ書けて簡単ですね。

Level 2

別の画面でinput要素を使うようになり custom component にした場合。

フォーム側

InputText.vue
<template>
  <input
    v-model="inputValue"
    :class="className"
    :name="name"
    :placeholder="placeholder"
    type="text"
    @input="$emit('input', $event.target.value)"
  >
</template>

<script>
export default {
  name: 'InputText',
  props: {
    className: {
      type: String, 
      default: ''
    },
    placeholder: { 
      type: String,
      default: 'placeholder'
    },
    name: { 
      type: String, 
      default: ''
    },
    value: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      inputValue: null
    };
  },
  watch: {
    value: function(newValue) {
      this.inputValue = newValue;
    }
  },
  mounted() {
    this.inputValue = this.value;
  }
};
</script>

親の v-modelprops value なので初回時のmounted、変更時の watch を使って、data inputValue にセットすることで自コンポーネントの v-model としています。
また @input を使うことで親の v-model に値を反映しています(checkbox だと @change)。

画面側

Order.vue
<InputText
  v-validate="{ required:true, max:10 }"
  v-model="order"
  :class-name="{ 'error': errors.has('order') }"
  name="order"
  data-vv-value-path="inputValue"
  placeholder="上ハラミ"
/>

data-vv-value-path に子コンポーネントの v-model の変数名を入れないと(この場合だとinputValue)、バリデーションに渡す値が遅延するので設定必須です(記載ないと親側の複数箇所でnextTickをする必要があります)

Level 3

画面に複数フォームになり、vuex を導入する必要がある段階。
それに加えてカスタムルールを用いてバリデーションをする場合。

画面側

Order.vue
<template>
  <div 
    v-for="(food, index) in foods"
    :key="index"
  >
    <InputText
      v-validate="{ required:true, max:10, custom-rule: [index] }"
      v-model="'order' + index"
      :class-name="{ 'error': errors.has('order' + index) }"
      :name="'order' + index"
      data-vv-value-path="inputValue"
      data-vv-as="注文"
      placeholder="上ハラミ"
      @input="onInput"
    />
  </div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import store from '~/src/store';

export default {
  name: 'Orders',
  store,
  data() {
    return {};
  },
  computed() {
    ...mapState(['orders']),
  },
  watch: {
    // 変更時に全てのバリデーションチェック
    orders() {
      this.checkCustomValidate();
    }
  },
  created() {
    // カスタムルールの追加
    this.$validator.extend('custom-rule', {
      getMessage: () => '拡張バリデーションのメッセージ',
      validate: () => this.orders.length > 10
    });
  },
  mounted() {},
  methods: {
    ...mapActions(['setOrders']),

    /**
     * フォームの値をstoreに反映
     */
    onInput(e) {
      this.setOrders(e.target.value)
    },

    /**
     * カスタムルールの確認
     */
    checkCustomValidate() {
      const errorBag = this.$validator.errors;
      const errors = errorBag.items.filter(item => item.rule === 'custom-rule');
      if (size(errors) > 0) {
        this.$validator.validateAll();
      }
    }
  }
};
</script>

vuex を使う場合には画面側で @input を使って store に反映をする必要があります。
またフォーム入力時に関連する他のフォームについてもバリデーションエラーを表示したい場合には、watch を使い store または data の変更を監視することで、 validateAll を使うことで、より実用に近いバリデーションの実装が可能になります。

VeeValidateでルールを定義せずに errors に対して add, update する方法も試してみましたが、そちらはデフォルトルールと一緒に定義した場合に挙動がおかしくなってしまったので使わないようにしました。

GWになったら書きたいなと思っていたので早速エントリーしてみました:muscle:
天気も良く最高ですね!みなさんよい休日をお過ごしください:sunglasses:

19
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
10