LoginSignup
8
6

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-06

PrAha Inc. CEO、時々エンジニアのdowannaです。

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

この記事で伝えたいこと

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

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

公式サイトにもある通り、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

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日後にリファクタリングします

8
6
2

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
8
6