Vueでバリデーションを行う方法

前回に続きまして、Vueでバリデーションを行う方法を紹介しようと思います。

調べたりしたのですが、自分の力では理解できる記事が少なかったので、備忘録として残しておきたいと思います。

今回は前回の動的にコンポーネントを追加する機能を付けたままバリデーションを行うようにしてあります。

# 開発環境
* MaOS
* VSCode
* vue.js 2.x
* vue/cli 4.5.13
* Bootstrap-vue
* veeValidate 7.7.6


# 参考にした記事
[Vue JS VeeValidate + BootstrapVue – Form Validation Example](https://www.positronx.io/vue-js-veevalidate-bootstrapvue-form-validation-example/)

# やり方

まずは、親のコンポーネントからです。

```Vue:Parent.vue
<template>
    <b-container fluid>
        <b-row class="mt-5 d-flex justify-content-center">
            <b-col cols md="8" xl="8">
                <b-row class="mb-5">
                    <b-col cols="12">
                        <ValidationObserver ref="observer">
                            <Children></Children>
                            <component v-for="field in fields" v-bind:is="field.type" v-bind:key="field.id"></component>
                            <b-button block type="button" v-on:click="addForm('Children')" class="border-0" style="background-color: rgba(191, 202, 63, 0.99);">観光地を追加</b-button>
                        </ValidationObserver>
                    </b-col>
                </b-row>
            </b-col>
        </b-row>
    </b-container>
</template>

<script>
import Children from '../components/Children.vue'
import { ValidationObserver } from 'vee-validate'

export default {
    data: () => ({
        tripTran: {
            title: '',
            days: '',
        },
        fields: [],
        count: 0,
    }),
    components: {
        Children,
        ValidationObserver,
    },
    methods: {
        addForm: function(type){
            this.fields.push({
                type: type,
                id: this.count++
            })
        },
    }
}
</script>
```
次に子のコンポーネントになります。

```Vue:Children.vue
<template>
    <div>
        <b-row class="justify-content-end d-flex mt-3 mb-3">
            <b-col cols="3">
                <ValidationProvider name="移動手段" rules="required">
                    <b-form-group slot-scope="{ valid, errors }">
                        <b-form-input
                            type="text"
                            v-model="tripTransport.howTo"
                            placeholder="移動手段"
                            v-bind:state="errors[0] ? false : (valid ? true : null)">
                        </b-form-input>
                        <b-form-invalid-feedback>
                            {{ errors[0] }}
                        </b-form-invalid-feedback>
                    </b-form-group>
                </ValidationProvider>
            </b-col>
            <b-col cols="3">
                <ValidationProvider name="所要時間" rules="required|numeric">
                    <b-form-group slot-scope="{ valid, errors }">
                        <b-form-input
                            type="text"
                            v-model="tripTransport.howLong"
                            placeholder="所要時間"
                            v-bind:state="errors[0] ? false : (valid ? true : null)">
                        </b-form-input>
                        <b-form-invalid-feedback>
                            {{ errors[0] }}
                        </b-form-invalid-feedback>
                    </b-form-group>
                </ValidationProvider>
            </b-col>
            <b-col cols="3">
                <ValidationProvider name="料金" rules="required|numeric">
                    <b-form-group slot-scope="{ valid, errors }">
                        <b-form-input
                            type="text"
                            v-model="tripTransport.fee"
                            placeholder="料金"
                            v-bind:state="errors[0] ? false : (valid ? true : null)">
                        </b-form-input>
                        <b-form-invalid-feedback>
                            {{ errors[0] }}
                        </b-form-invalid-feedback>
                    </b-form-group>
                </ValidationProvider>
            </b-col>
        </b-row>
        <b-row>
            <b-col cols="4">
                <ValidationProvider name="観光地" rules="required">
                    <b-form-group slot-scope="{ valid, errors }">
                        <b-form-input
                            type="text"
                            v-model="tripTransport.spot"
                            placeholder="観光地"
                            v-bind:state="errors[0] ? false : (valid ? true : null)">
                        </b-form-input>
                        <b-form-invalid-feedback>
                            {{ errors[0] }}
                        </b-form-invalid-feedback>
                    </b-form-group>
                </ValidationProvider>
            </b-col>
            <b-col cols="4">
                <ValidationProvider name="滞在時間" rules="required|numeric">
                    <b-form-group slot-scope="{ valid, errors }">
                        <b-form-input
                            type="text"
                            v-model="tripTransport.hours"
                            placeholder="滞在時間"
                            v-bind:state="errors[0] ? false : (valid ? true : null)">
                        </b-form-input>
                        <b-form-invalid-feedback>
                            {{ errors[0] }}
                        </b-form-invalid-feedback>
                    </b-form-group>
                </ValidationProvider>
            </b-col>
            <b-col cols="4">
                <ValidationProvider name="料金" rules="required|numeric">
                    <b-form-group slot-scope="{ valid, errors }">
                        <b-form-input
                            type="text"
                            v-model="tripTransport.money"
                            placeholder="料金"
                            v-bind:state="errors[0] ? false : (valid ? true : null)">
                        </b-form-input>
                        <b-form-invalid-feedback>
                            {{ errors[0] }}
                        </b-form-invalid-feedback>
                    </b-form-group>
                </ValidationProvider>
            </b-col>
        </b-row>
    </div>
</template>

<script>
import { ValidationProvider } from 'vee-validate'

export default {
    componets: {
        ValidationProvider
    },
    data: () => ({
            tripTransport: {
                spot: '',
                hours: '',
                money: '',
                howTo: '',
                howLong: '',
                fee: '',
            }
    }),
}
</script>
```

# 解説
それでは解説をしていきたいと思います。

まず、`veevalidate`を使用してバリデーションを行うには、`ValidationObserver`と`ValidationProvider`というものをimportする必要があります。

`ValidationObserver`でフォーム全体を囲むようにして、`ValidationProvider`で個々のフォームを囲むという感じです。

## ValidationObserver
`<ValidationObserver>`タグ内には、`ref="observer"`を書く必要があるみたいです。
参考にした記事の中では、`<b-form>`タグを作成しておりましたが、`動的にコンポーネントを追加しようと思ったときに、上手くできなかった`ので、今回は使っておりません。

```Vue
<template>
<ValidationObserver ref="observer">
    <b-form slot-scope="{ validate }" 
v-on:submit.prevent="validate().then(handleSubmit)">
    </b-form>
    <b-button type="submit">送信</b-button>
</ValidationObserver>
</template>

<script>
export default{
    methods: {
         handleSubmit(){
              console.log('hello')
}}
}
</script>
```

## ValidationProvider
`<ValidationProvider>`タグ内には、`rules`を書くようにします。
この`rules`の中に`required`を入れれば`必須項目`になったりと、バリデーションのルールを設定します。

```Vue
<ValidationProvider name="移動手段" rules="required">
    <b-form-group slot-scope="{ valid, errors }">
        <b-form-input
            type="text"
            v-model="tripTransport.howTo"
            placeholder="移動手段"
            v-bind:state="errors[0] ? false : (valid ? true : null)">
        </b-form-input>
        <b-form-invalid-feedback>
            {{ errors[0] }}
        </b-form-invalid-feedback>
    </b-form-group>
</ValidationProvider>

<script>
import { ValidationProvider } from 'vee-validate'

export default {
    data: () => ({
            tripTransport: {
                spot: '',
                hours: '',
                money: '',
                howTo: '',
                howLong: '',
                fee: '',
            }
    }),
   component: {
           ValidationProvider
   }
}
</script>
```
`slot-scope`と`v-model`、`v-bind:state`、`<b-form-invalid-feedback>`を記述します。
`slot-scope`と`v-bind:state`がどういう仕組みなのかは自分もわかって無いです。
ただ、`v-bind:state`でバリデーションがエラーがどうかを判定していると思われます。

`v-model`は`script`内に記述した`data`と入力内容を結びつける役割をします。
ただ、なんでこれを記述する必要があるのかは自分もわかりません。

`b-form-invalid-feedback`はその後ろに`{{ errors[0] }}`を持ってきて、エラー文を表示するようにしてあります。

# まとめ
バリデーションには手こずりましたので、どなたかの助けになればと思います。
バリデーションは色々な場合を考えなきゃいけないことが多いので大変だなと思いました。
地道に書こうかと思っておりましたが、veeValidateが使えたので良かったです。
地道に書くよりもveeValidateを使う方が楽なのかなと思います。