【Vuelidate】親子間での利用

子コンポーネントに**ref**で値を設定してやれば
**this.\\$refs**から子コンポーネントの状態を取ってくることができます。
今回の場合は**this.\\$refs["vali-files"].\\$v**からvuelidateの機能が利用できる。
利点は子に各vuelidateのルールを切り分けるので、フォームが増えても１ファイル１ルールで肥大化が避けられます。

```vuejs:親コンポーネント
<template>
  <form>
    <Files ref="vali-files" />
    <v-btn class="mr-4" @click="mySubmit">出力</v-btn>
    <v-btn @click="myClear">リセット</v-btn>
  </form>
</template>
<script>
import Files from "~/components/files.vue";
import { mapState, mapMutations, mapGetters, mapActions } from "vuex";
export default {
  components: { Files },
  data({}) {
    return {
      valis: {},
      comps: {},
    };
  },
  computed: {
    ...mapState("state-cms", ["$postData"]),
  },
  methods: {
    mySubmit() {
      let errorLen = 0;
      for (let key in this.valis) {
        this.valis[key].$touch();
        if (this.valis[key].$invalid) errorLen++;
      }
      //失敗…　returnで中断
      if (errorLen) return;
      //成功！以下サーバー処理
      //this.exportFiles();
    },
    myClear() {
      for (let key in this.valis) {
        this.valis[key].$reset();
      }
      this.comps["vali-files"].files = [];
    },
  },
  created() {},
  mounted() {
    for (let key in this.$refs) {
      if (key.match(/^(vali-)/) !== null) {
        this.comps[key] = this.$refs[key];
        this.valis[key] = this.$refs[key].$v;
      }
    }
  },
};
</script>
```

```vuejs:子コンポーネント
<template>
  <v-container>
    <v-row
      class="text-center"
      @dragover.prevent
      @dragenter="onDragEnter"
      @dragleave="onDragLeave"
      @drop="onDrop"
    >
      <v-file-input
        v-model="files"
        color="deep-purple accent-4"
        counter
        label="参照するJSON：例… ~archive/000xxxx.json ※ドラッグ＆ドロップ可"
        multiple
        required
        placeholder="あなたはファイルを選ぶ"
        prepend-icon="mdi-paperclip"
        outlined
        :show-size="1000"
        :background-color="isDragging ? 'blue' : 'null'"
        :error-messages="filesErrors($v.files)"
        @change="$v.files.$touch()"
        @blur="$v.files.$touch()"
      >
        <template v-slot:selection="{ index, text }">
          <v-chip
            v-if="index < 2"
            color="deep-purple accent-4"
            dark
            label
            small
            >{{ text }}</v-chip
          >
          <span
            v-else-if="index === 2"
            class="overline grey--text text--darken-3 mx-2"
            >+{{ files.length - 2 }} File(s)</span
          >
        </template>
      </v-file-input>
    </v-row>
  </v-container>
</template>
<script>
import { mapState, mapMutations, mapGetters, mapActions } from "vuex";
import { validationMixin } from "vuelidate";
import { required, maxLength, email } from "vuelidate/lib/validators";
export default {
  mixins: [validationMixin],
  validations: {
    files: {
      required,
      //jsonかどうか正しいかどうかを判別する
      async chkJson(val) {
        if (!val.length) return false;
        const jsonFlg = val[0].type.match(/json/);
        if (jsonFlg === null) {
          return false;
        } else {
          const reader = new FileReader();
          await this.files[0].text().then((data) => {
            this.$updatePostData({ key: "srcJson", val: data });
            this.$updatePostData({ key: "srcJsonName", val: val[0].name });
          });
          return true;
        }
      },
    },
  },
  props: [],
  data({}) {
    return {
      isDragging: false,
      dragCount: 0,
      files: [],
    };
  },
  computed: {},
  methods: {
    ...mapMutations("state-cms", ["$updatePostData"]),
    onDrop(e) {
      e.preventDefault();
      e.stopPropagation();
      this.isDragging = false;
      const _files = e.dataTransfer.files;
      for (const file in _files) {
        if (!isNaN(file)) {
          //filesはファイル以外のデータが入っており、ファイルの場合のみキー名が数字になるため
          this.files.push(_files[file]);
        }
      }
      this.files =
        this.files.length > 1
          ? this.files.splice(1, this.files.length - 1)
          : this.files;
    },
    onDragEnter(e) {
      e.preventDefault();
      this.isDragging = true;
      this.dragCount++;
    },
    onDragLeave(e) {
      e.preventDefault();
      this.dragCount--;
      if (this.dragCount <= 0) {
        this.isDragging = false;
      }
    },
    filesErrors(_files) {
      const errors = [];
      if (!_files.$dirty) return errors;
      if (!_files.required) {
        errors.push("Form is required.");
        return errors;
      }
      !_files.chkJson && errors.push("JSONじゃない！！");
      return errors;
    },
  },
};
</script>
```