100日後にリファクタリングするVuex（Vuexの使い方を間違えた件）

PrAha Inc. CEO、時々エンジニアの[dowanna](https://twitter.com/dowanna6)です。

nuxt initしたら勝手に付いてくるため無意識に使いがちなVuexですが、本来の用途を理解せず使うと、ただ労力が増えるだけで、さほど意味のない構成になります。僕は見事にそれをやりました。そんな僕を反面教師に、Vuexとの正しい付き合い方を感じてもらえたら幸いです。


# この記事で伝えたいこと
Vuexは「複数のcomponentでstateを共有すること」が本来の意図されたユースケースで、stateをcomponent間で共有しない場合は、無理にvuexを使わなくても構わない


## Vuexは「複数componentでstateを共有すること」を想定

[公式サイト](https://vuex.vuejs.org/)にもある通り、Vuexは「複数componentでstateを共有する時」を想定しています。

が、僕が作ったアプリケーションでは**nuxtのpage単位にVuexのstoreを分割しました。**


hogehoge.com/a
hogehoge.com/b

という2つのpageがあったとすると、こんな構成でした

```
nuxt
├──-pages
    ├──a.vue
    ├──b.vue
├──-store
    ├──a.js
    ├──b.js
```

page/Aからdispatchされたactionはstore/Aでハンドリング
page/Bからdispatchされたactionはstore/Bでハンドリング

こんな具合にstore同士がお互いにやり取りをしない構成になっていましたが、特にcomponentを跨がないデータを取り扱うだけであれば**pageの中にdataとして持っても構わないわけです。**単純にpageのロジックをstoreに移すだけではメリットは少なく、以下のようなデメリットが気になり始めました。


## Vuexのデメリットが目立つように

### Publicなストアである
Vuexは他のpageからも容易に編集できるグローバルなpublicストアなので、いつどこから編集されるか分かりません。

開発ツールでデバッグして追うことは出来ますが、pageのなかにdataとして持っていた方が、変更の呼び出し箇所が限定されて保守性も高まるでしょう。

グローバルストアに四方八方からガンガンデータを突っ込む、という一番やってはいけない事をやってしまった。


### コードジャンプが効かない
Vuexを使う場合はメソッドの呼び出しではなく文字列をキーに使ったイベント送信になるためコードジャンプが効きません。

一見地味なデメリットに見えますが、ずっと開発しているとイライラしてきます。あと一括で変更したい時にRename Symbol（VSCodeならF2）が効かないのでいちいちgrepして置換する必要が生じます。

```
this.$store.dispatch('a/doSomething') // <- 変更箇所を探るためにはgrepするしかない
```

### dispatchやmutationの記述量が増えた
pageのdataとして保持していればapi呼び出しの一行で終わるところ、VuexだとAPIと通信するたびにdispatchして、mutationして、stateに反映する必要が生じます

```
// ====== Vuexなし ====== //
const result = await get('/a') // たった1行だ！

// ====== Vuexあり ====== //
// page/a.vue
this.$store.dispatch('a/doSomething')

// store/a.js
export const state = () => ({
  result: ''
})

export const actions = {
  async doSomething({ commit }) {
    const data = await get('/a')
    commit('setSomething', data)
  }
}

export const mutations = {
  setSomething(state, data) {
    state.result = data.result
  }
}
```

更にpage/a.vueでmapSetterしてstoreからdataに取り出さなければいけない・・・この辺りからVuexを使うのではなくVuexに使われている感覚に陥ってきます


### dispatchした結果を待つときにfluxパターンが崩壊する
APIに対するget('/a')の結果を待ってから処理を継続したい時、こんな感じでdispatchを待ちたくなります

```
const data = await this.$store.dispatch('a/doSomething')
```

が、これはfluxパターンに違反しています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246648/edb873d3-3c30-1c77-6b1b-a944ceb01134.png)

Vue components から dispatch された actionが逆流することになるからです。正しくfluxパターンでdispatchの結果を待つのであれば、dispatchの結果変化するstateを監視する必要が生じます。が、返却値は必要無いのに、処理を待ちたい時まで無駄なstate変数が増えるのはイマイチです。


こんな感じのデメリットに殴り続けられながら、現状の構成で得られるメリットが殆ど無いことに気づいた時、僕はもう限界だと気づき、Vuexをやめたくなった。


## そもそも、どうしてVuexを使い始めたのか？
component間でデータを共有しない以上、ただ手続きを煩雑にするだけのグローバルストアと化してしまったわけですが、そもそもなぜ僕はVuexを使ったのでしょうか。

**nuxtに最初から入っていたから自然と使ってしまった**と、人のせいにしてみます。

それは冗談ですが、当初は「APIにリクエストを投げる作業とか、ビューから描画以外のロジックを切り出すのにちょうどいいぞ」と考えていました。componentは出来る限りビューの描画に専念させたい。責務を分担したい。清純な動機でVuexを使い始めました。

しかし結論から言うと**「APIにリクエストを投げる作業を切り出す」**に関してはVuexを入れなくても、APIServiceみたいなクラスをpluginとして定義することや、utilにAPI呼び出しを切り出すことで実現できます。


```
//infrastructure/apiService.js

export default class APIService {
  constructor(axios) {
    this.axios = axios
  }
  get(url, options = {}) {
    return this.axios.$get(url, options)
  }
}
```

コイツをextendしたラッパーを用意して...

```
// infrastructure/settingAPIService.js

import APIService from './apiService'
export default class SettingAPIService extends APIService {
  getUserSettings() {
    return this.get('/settings')
  }
}
```

pluginとして注入すれば

```
// plugins/apiService.js
export default ({ $axios }, inject) => {
  const settingAPIService = new SettingAPIService($axios)
  inject('settingAPIService', settingAPIService)
}
```


componentからapiServiceを呼び出せるようになります。これでAPI呼び出しの詳細(axiosに関する知識とか)はpageから切り出されました。

```
// pages/a.vue
const setting = await this.$settingAPIService.getUserSettings()
```

pluginとして定義すると多少はパフォーマンスも悪化すると思いますが、自分たちが書いているコード量しかないので、pluginにしてもさほど変化はないと考えました。ここまでやらなくても、utilに用意したメソッドを呼び出してもいいと思います。



## 総括

pageのdataで事足りるのにVuexを採用した結果

・Publicなグローバルストアが量産されて、保守性が落ちた
・コードジャンプが効かずイライラした
・無駄にコードの記述量が増えた
・dispatchした結果を待つ時にfluxパターンが崩壊する、もしくは無駄にstateが肥大化して保守性が犠牲になる


**教訓：何かを使う時は、それが想定されているユースケースをちゃんと調べよう**


100日後にリファクタリングします