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