【Vue.js】ダブルサブミット対応なコンポーネントを作ってみよう。

## 経緯
ダブルサブミット対応をする必要が発生した。けど、これっていろんな画面で使われるからコンポーネントで用意した方が良いよね？よし、やってみっか！って感じで始めました。

## やりたいこと
以下のような、いたってシンプルな仕様を目指してコンポーネントを作ってみます。

 - サブミットされた時に処理中なら非活性
 - 処理中の状態は親と共有できるように

ちなみに今回はMixinsを使わなかったです。理由としてはダブルサブミット対応が必要なコンポーネントが一つだったからです。
新たに対応が必要なコンポーネントが発生したらMixinsを使うべきだろうな〜と思っています。

## 方法1：Vuexを使って状態管理する
結論を先に言うと、このやり方は不採用です。

「状態を管理する=Vuexかな！」と思って試してみました。

親から状態を参照して、子は状態を変更できるような仕組みにしました。
本当はページごとに状態は別々で存在するのだから、モジュールに分割したかった。でも親から子に対してモジュールを指定する作りが分からなかった。（できるのか？）

そして出来上がったのがこちら。

```store/index.js
export const state = () => ({
  processing: false
})

export const mutations = {
  activateProcessing(state) {
    state.processing = true
  },
  deactivateProcessing(state) {
    state.processing = false
  }
}
```

```ParentComponent.vue
<template>
  <!-- isCorrectFormはフォームの妥当性を検証する算出プロパティ -->
  <btn-submit
    :is-disabled="!isCorrectForm"
    :onclick="save">登録
  </btn-submit> <!-- saveメソッドは親が持っているメソッド -->
</template>
```

``` BtnSubmit.vue
<template>
  <button
    :disabled="processing || isDisabled"
    class="button"
    @click="handleClick">
    <slot>submit</slot>
  </button>
</template>
<script>
export default {
  name: 'BtnSubmit',
  props: {
    onclick: {
      type: Function,
      required: true
    },
    isDisabled: {
      type: Boolean,
      required: false,
      default: false
    }
  },
  computed: {
    processing() {
      return this.$store.state.processing
    }
  },
  methods: {
    handleClick(event) {
      if (this.processing) return
      this.$store.commit('activateProcessing')
      this.onclick(event).then(() => {
        this.$store.commit('deactivateProcessing')
      })
    }
  }
}
</script>
```

**できたけど、なんかいけてねえな・・・**
処理が中断した際にちゃんとリカバリできる？とか、そもそもルートモジュールを使う必要性ある？とか、解消できていない要素が見えてしまうなぁ。。。

## 方法2：親の状態を子に渡して変更してもらう
Vuexを利用すると複数コンポーネントに渡って状態を共有できるが、「処理中」状態は果たしてVuexで管理するべきなのか？

個人的な見解は以下の通り。

「Vuexは一つの状態を複数コンポーネントで共有する場合に適切。処理中という状態は一種類の状態だが、ページごとに処理中の状態は発生するため、一つの状態ではない。よって不適切。」

状態はそれぞれのページ（親コンポーネント）から渡すような仕組みにしたい。
考えた実現方法は以下の2パターン。

一番悩んだことは、event.preventDefaultを親と子どちらで処理するべきか。
これまた先に結論としては、今回はパターン２を採用した。

### パターン１：子でpreventDefaultを処理
```ParentComponent.vue
<template>
  <btn-submit
    :is-disabled="!isCorrectForm"
    :processing="processing"
    :onclick="save"
    :does-prevent="true"
    @updateProcessing="updateProcessing"
  >更新する</btn-submit>
</template>

<script>
asyncData() {
  return {
    processing: false
  }
}

methods: {
  updateProcessing(value) {
    this.processing = value
  }
}
</script>
```

```BtnSubmit.vue
<template>
  <button
    :disabled="processing || isDisabled"
    class="button"
    @click="handleClick">
    <slot>submit</slot>
  </button>
</template>

<script>
export default {
  name: 'BtnSubmit',
  props: {
    onclick: {
      type: Function,
      required: true
    },
    isDisabled: {
      type: Boolean,
      default: false
    },
    processing: {
      type: Boolean,
      default: false
    },
    doesPrevent: {    // event処理方法を判別する
      type: Boolean,
      default: false
    }
  },
  data: function() {
    return {
      internalProcessing: this.processing
    }
  },
  watch: {
    internalProcessing(value) {
      this.$emit('updateProcessing', value)
    }
  },
  methods: {
    handleClick(event) {
      if (this.doesPrevent) {
        event.preventDefault()
      }
      if (this.processing) return
      this.internalProcessing = true
      const self = this
      this.onclick(event).then(() => {
        self.internalProcessing = false
      })
    }
  }
}
</script>
```
#### メリット
 - preventDefaultの処理が一元化されて冗長的にならない
 - propsで渡すためeventのハンドリング漏れが無くなる

#### デメリット
 - 渡すべきpropsが多くなってしまう。

### パターン２：親でpreventDefaultを処理
```ParentComponent.vue
<template>
  <btn-submit
    :is-disabled="!isCorrectForm"
    :processing="processing"
    :onclick="save"
    @updateProcessing="updateProcessing"
  >更新する</btn-submit>
</template>

<script>
asyncData() {
  return {
    processing: false
  }
}

methods: {
  updateProcessing(value) {
    this.processing = value
  }
}
</script>
```

```BtnSubmit.vue
<template>
  <button
    :disabled="processing || isDisabled"
    class="button"
    @click="handleClick">
    <slot>submit</slot>
  </button>
</template>

<script>
export default {
  name: 'BtnSubmit',
  props: {
    onclick: {
      type: Function,
      required: true
    },
    isDisabled: {
      type: Boolean,
      default: false
    },
    processing: {
      type: Boolean,
      default: false
    }
  },
  data: function() {
    return {
      internalProcessing: this.processing
    }
  },
  watch: {
    internalProcessing(value) {
      this.$emit('updateProcessing', value)
    }
  },
  methods: {
    handleClick(event) {
      if (this.processing) return
      this.internalProcessing = true
      const self = this
      this.onclick(event).then(() => {
        self.internalProcessing = false
      })
    }
  }
}
</script>
```


#### メリット
 - event処理が親で自由に行える


#### デメリット
 - 全ての親がeventの処理を行う必要がある

結論として**パターン２**を採用。採用した理由は以下の２点。

 - コンポーネントは汎用的かつ使いやすくするためpropsを渡しすぎない方が良い
 - イベントの処理は発生させた側でハンドリングの責務を負うべき

## あとがき
コンポーネント設計は、ベストプラクティスなぞケースバイケースだと思っているものの、いろんな方の考え方は是非聞いてみたいところです。「こんな実現方法がもっと良いよ！」「こんな考え方が良いよ！」などありましたら教えてください！

今後はやっぱりAtomic Designに沿ったコンポーネント設計を極めていきたいな。

## 参考にしたもの
 - [[Vue.js] フォームの多重送信を防止する](https://qiita.com/ryo511/items/2563bb61b4b4d1be9619)