2018-06-08

Vuex PersistedStateの値の復帰のタイミングが謎だったのでNuxtのplugin書いて解決した

背景

先日から 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 を使っていなくても参考になるし、今後自分で参考にしたいので書いてます。

使っているもの

おさらい - Nuxtを利用したVueアプリにきっとありがちなこと -

  1. Vuex PersistedState 経由で localStorage から値が復帰するのは mounted() 以降
  2. this.$route や this.$store に依存するコードが安定して動くのも mounted() 以降
  3. 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/ つまりレールから外れた場所に置くコードを増やしてやるのが吉っぽい。

です!

[^1]

参考

About

例によって個人のなんちゃらです

Recent Posts

Categories

Tool 日々 Web Biz Net Apple MS ことば News Unix howto Food PHP Movie Edu Community Book Security Text TV Perl Ruby Music Pdoc 生き方 RDoc ViewCVS CVS Rsync Disk Mail FreeBSD Cygwin PDF Photo Zebedee Debian OSX Comic Cron Sysadmin Font Analog iCal Sunbird DNS Linux Wiki Emacs Thunderbird Sitecopy Terminal Drawing tDiary AppleScript Life Money Omni PukiWiki Xen XREA Zsh Screen CASL Firefox Fink zsh haXe Ecmascript PATH_INFO SQLite PEAR Lighttpd FastCGI Subversion au prototype.js jsUnit Apache Trac Template Java Rhino Mochikit Feed Bloglines CSS del.icio.us SBS qwikWeb gettext Ajax JSDoc Rails HTML CHM EPWING NDTP EB IE CLI ck ThinkPad Toy WSH RFC readline rlwrap ImageMagick epeg Frenzy sysprep Ubuntu MeCab DTP ERD DBMS eclipse Eclipse Awk RD Diigo XAMPP RubyGems PHPDoc iCab DOM YAML Camino Geekmonkey w3m Scheme Gauche Lisp JSAN Google VMware DSL SLAX Safari Markdown Textile IRC Jabber Fastladder MacPorts LLSpirit CPAN Mozilla Twitter OpenFL Rswatch ITS NTP GUI Pragger Yapra XML Mobile Git Study JSON VirtualBox Samba Pear Growl Mercurial Rack Capistrano Rake Win RSS Mechanize Sitemaps Android JavaScript Python RTM OOo iPod Yahoo Unicode Github iTunes God SBM friendfeed Friendfeed HokuUn Sinatra TDD Test Project Evernote iPad Geohash Location Map Search Simplenote Image WebKit RSpec Phone CSV WiMAX USB Chrome RubyKaigi RubyKaigi2011 Space CoffeeScript Nokogiri Hpricot Rubygems jQuery Node GTD CI UX Design VCS Kanazawa.rb Kindle Amazon Agile Vagrant Chef Windows Composer Dotenv PaaS Itamae SaaS Docker Swagger Grape WebAPI Microservices OmniAuth HTTP 分析基盤 CDN Terraform IaaS HCL Webpack Vue.js BigQuery Middleman CMS AWS PNG Laravel Selenium OAuth OpenAPI GitHub UML GCP TypeScript SQL Hanami Document SVG AsciiDoc Pandoc DocBook Develop Jekyll macOS Node.js Vite Heroku Transformer AI Data Cloud Wasm