Vuex導入時のチェックポイント・考えるべきこと

会社でのプロジェクトにVuexを導入した。
その時に考えたこと、考えるべきだったこと、使い始めてから調べたことなどをまとめる。

# そもそもVuexとはなにか
結論から言えば、Vueアプリ全体から参照できる共通のデータストアを作れるライブラリである。

[公式docs](https://vuex.vuejs.org/)を読めばいいと思うが、簡単にここでもまとめます。

Vuexとは、Vue.js製アプリケーションのための状態管理パターン、及びそのためのライブラリの名前である。
Vuexはアプリ内の全てのコンポーネントから利用可能な、集中型のデータストアを提供する。

Vue公式のブラウザ向け拡張機能とも勝手に統合してくれる。
設定無しで過去データに遡ってのデバッグや、状態のスナップショットのexport / importができたりする。

# Vuexを使う意味が本当にあるのか
個人的に判定ポイントは3つある。

1. 作ろうとしているアプリ、もしくは今作っているアプリの規模はでかいか
2. 短期的な効率を求めるか、長期的な効率を求めるか
3. Vuexを使ってみたいと思えるか

正直3があるなら1, 2はサブの理由だと思う。

# TypeScriptで行ける？
行けた。特に問題ないが、後述するmodule周りのtypeの扱いだけちょっと詰まった。

# IE対応・・・ (2018/08/31 追記)
~~IEなくなればいいのに~~

一応行けます。
ただ、Vuexパッケージ内でPromiseに依存しているので、babelなどで対応してあげる必要があります。

~~ほんとIEは産廃~~

# プロジェクト構成
完全に公式ドキュメントの受け売りだが、下記がサンプル。
ただ、別にこの通りにする必要はない。

```
├── index.html
├── main.js
├── api
│   └── ... # APIリクエストを行う部分を抽出したディレクトリ
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # modulesをまとめ上げて、1つのstoreとしてexportするやつ
    ├── actions.js        # ルートstoreのaction
    ├── mutations.js      # ルートstoreのmutation
    └── modules           # storeを細分化したやつを格納するディレクトリ
        ├── cart.js
        └── products.js
```

単純に既存プロジェクトにVuexを追加するだけなら、上記のうち`store`ディレクトリを追加するだけでも事足りるはず。
さらに言えば、`store/modules`もなくても行ける。

ただ、そもそもVuexを入れるほどの規模のアプリなら分けてたほうが良いよねっていうのはある。

### apiディレクトリ is 何
多くのWebアプリケーションはAPIサーバにリクエストを投げて必要な情報を取得してくることがある。
そのリクエストまわりの処理を抜粋してここにぶちこんでおくためのディレクトリ。

axiosでもkyでもfetchでも何でも良いけど、そいつらを使ったリクエストは全部ここに置いとく。

ぶっちゃけここに集積しておかなくてもアプリは動かせるが、確かにリクエスト周りだけここに置いとけば使い回しも効くしVuex storeからのAPIコールもすっきりするので、まあ言われる通り作っておいていいのでは。

正直そこまでしっくりは来ていない。Vuexだから必要とかいうわけではないと思う。

# Stateには何でも保存して良いのか
結論から言うと否。

これはコードレビュー中に、APIから取得したデータをそのままstateに突っ込んでいるのを見て疑問に思ったこと。

Stateに入れるべきかどうかで悩んだら下記参照。 (Redux docの受け売りです)

1. アプリケーション全体で、そのデータを必要とする部分が他にあるか
2. そのデータを元にして別のデータを作る必要があるか
3. そのデータは複数のコンポーネントに利用されているか
4. そのデータの過去時点での状態をチェックできることに価値があるか (デバッグなどを想定)
5. そのデータをキャッシュしたいか (再リクエストするよりも、ステート内にあるデータを使ったほうが良いケースがあるか)
6. ホットリロードの間に失われたくないデータか

出典: https://redux.js.org/faq/organizing-state#do-i-have-to-put-all-my-state-into-redux-should-i-ever-use-reacts-setstate

そもそも、stateに保存しなくていいデータだったらわざわざVuexを介する必要もない。

# どうやってデータを保持するか
大層な名前はついているが、結局実態はでかいオブジェクトである。

普通にリロードされたりタブ消したりされると当然のごとく消える。
でも保持したいこともある。というかそのケースのほうが多い気がする。

store保持が気になるケースとしては下記が考えられる (他にもあるかも)

- リロードされる (リロードにも種類がある)
- タブが閉じられる
- ブラウザが閉じられる

特にログイン状態を保持したい場合など。

で、webStorageの出番である。

> ちなみにWebを漁ってると「localStorageにJwtTokenを入れて永続化や！どや！」
みたいな記事を死ぬほどよく見かけますが、やっちゃダメです。
localStorageの中身はJavaScriptで簡単に抜き取れるので。
ここで言ってるのは、isLoggedInなどといった「ユーザーが過去にログインした履歴があるか」という情報の保持のことです。

リロード対策のみならsessionStorageでOK
タブ閉じやブラウザ閉じまで対策したいならlocalStorageでOK

一応Cookieにも保存できるけど、許容容量めちゃんこ少ないので心もとないかな。

この辺のストレージに自分で保存しに行ってもよいけどめんどくさいので、

[vuex-persistedstate](https://github.com/robinvdvleuten/vuex-persistedstate) を使いましょう。

勝手にハンドリングしてくれて便利。

# 複数タブ間でどうやってデータを共有するか
Webアプリを使いこなす人、複数タブ開きがち

あるあるかどうかわかりませんが、実際そういう人もいると思います。
僕とかだと、AWSコンソールはタブ開きまくります。

タブAで行った変更がタブBにうつった時に勝手に反映されてたらかっこよくね！？みたいなのってあると思います。
多分実用的な要件としてもありえなくはない。

これについても[vuex-shared-mutations](https://github.com/xanf/vuex-shared-mutations)というプラグインがあります。

中身をさらっと覗いてみたら、

1. 共有したいmutationをプラグインに登録する
2. プラグイン内部では、あらゆるmutationが発火するたびにそのmutationが1で指定されたものかどうかを判定する
3. 指定されたものであった場合、他タブにEventでmutationを伝播させる

という感じのことをやってました。
割といいプラグインな気がしますが、スター数が少ないのが気になりますね。

みんなこれくらいだったら自分で作っちゃうわ、って感じなのかな。
優秀ですね。

# モジュール化の単位
Vuexのstoreは[プロジェクト構成]でも言及した通り、module分割できる。

どんな粒度でモジュール化すべき？というのはやっぱり気になる。

そもそもVuexは状態管理のライブラリなので、状態管理をしたい粒度で分割するのが正しいのだろう、おそらく。
機能単位になっていくのかなー。

ここはベストプラクティスがよくわかっていない。
ドメインドリブン？

# vue-routerとの共存
具体的には、vue-routerの処理中でstoreの中身を参照したい場合。

なんてことないのだが、普通にstoreをimportしてくれば良い。
すごく適当なサンプルが下記。

```typescript:router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import routes from './routes'
import store from '@/store' // 普通にimportして

Vue.use(VueRouter)

const router = new VueRouter({
  routes,
  scrollBehavior() {
    return { x: 0, y: 0 };
  }
})

router.beforeEach((to, from, next) => {
  const isLoggedIn = store.getters['isLoggedIn'] // 使いたいとこで使えばいい
  const isSessionExpired = store.getters['isSessionExpired'] // 使いたいとこで使えばいい
  const requireAuth = to.meta && to.meta.requireAuth

  if (!requireAuth) {
    return next()
  }

  if (isLoggedIn && !isSessionExpired) {
    next()
  } else {
    next({ path: '/login', query: { redirect: to.name || '' }})
  }
})

export default router
```

# Vueコンポーネント以外からVuex storeを使いたい
上と同じ。普通にimportすれば良い。

# まとめ
なんか追加することあればまた追記します