背景
先日から Vue (Nuxt) + Vuex + Vuex PersistedSate でモノを動かしてるんだけど、
なんかふいにデータが矛盾して動作がおかしくなる
ことが起きていた。
まぁアタリはついていて、Nuxtでrouteに応じてVuex Storeをmodule分割する方法 - あーありがち(2018-04-19) にある動的に module 構造の Vuex Store を生成している処理と、 Vuex PersistedState を通じて localStorage からデータが返ってくる処理がそれぞれバラバラに動いていることで、「Vuex Store の値を見て判定する処理」がタイミング次第の微妙なシロモノになっているんだろうなぁと思ってたんだけど、決定的な解決策が見つからないでいた。
通常の使い方だと問題にならないけどテストしてるとよく踏み抜くので、ごまかしごまかしやっていたのだが、今回解決できたので記録しておく。
※ ちなみに今回の話は Vuex PersistedState が localStorage の値でメモリ上のデータを書き換え終わったら Vuex がそれを通知してくれて Vue コンポーネントの computed に反映できれば、実はあまり悩まなくて済んだのだけど、対策としてやったこと自体は Vuex PersistedState を使っていなくても参考になるし、今後自分で参考にしたいので書いてます。
使っているもの
- Vue.js
- Nuxt.js - Universal Vue.js Applications
- robinvdvleuten/vuex-persistedstate: 💾 Persist Vuex state with localStorage.
おさらい - Nuxtを利用したVueアプリにきっとありがちなこと -
- Vuex PersistedState 経由で localStorage から値が復帰するのは mounted() 以降
- this.$route や this.$store に依存するコードが安定して動くのも mounted() 以降
- Nuxt では基本的にアプリの全体像を気にしなくても済むように Page component を突っ込むと routing も自動解決する、逆に言うとアプリ全体を組み立てるプロセスに介入するのは難しい
これらが合わさると何が起きるかというと、
Page component の mounted() が複雑で長大な一つのメソッドになりやすく、しかもタイミング問題まで抱え込んでしまう。
これ割と地獄感あるのですよ。
解決方法
Vuex Storeの初期化はpluginで行う
plugin function からは
- 初期化済みの store オブジェクト
- page component の有無から自動で解決される routes オブジェクト
の両方にアクセスすることができる。したがって、Nuxtでrouteに応じてVuex Storeをmodule分割する方法 - あーありがち(2018-04-19) でやったように
this.$store.registerModule(NAME, OBJECT)
ではなく、plugin の中で
export default ({app, store} => {
store.registerModule(NAME, OBJECT)
})
みたいにすると、Vue の root component が初期化される前にすべてを揃えることができる。
mounted()の中でsetTimeout
Vuex Store に localStorage から値が返ってくるのは mounted() 以降なのだが、値が返ってきてからでないとメモリ上の値で reset することはできない。
ということで以下のような感じになる。
mounted() {
setTimeout(() => {
resetの処理({値1, 値2, ..})
}, 0)
}
あれ、結局 mounted() の中が複雑なのは一緒じゃね?と思うかもしれないが、mounted() の中で registerModule すると、localStorage から値が返ってくるタイミングも遅くなり、かなり制御が難しくなるので、
mounted より前に registerModule しておいてからの setTimeout
という扱いにしておくのがよい。また、setTimeout() の中も直接長いコードを書かずに、遅延させてるのが分かる reset 処理だけ独立させた PureJS な function として切り出して、そいつに外からバンバン値を与えてやるのがよさそう。なんなら this.$route とか this.$store とかバンバン与える。
※ なんで reset したいのかはアプリによると思うが、保存しておいた何かと比較して違ってたら何か処理を行いたい、という要求は普通によくあると思う。
まとめ
Nuxt + Vue アプリを書く際には、
- Vue component ではない形で解決できるものはないか考え、どんどん分離する
- plugin は root component 初期化前に async/await で動作するのでよいぞ
を覚えておくのがよさそう。
特に、意外と View に関わらないコードは多くなるので、積極的に Vue component 以外の形にして、Nuxt のディレクトリレイアウトで言う components/ つまりレールから外れた場所に置くコードを増やしてやるのが吉っぽい。
です!