Nuxt.jsのストアをモジュールモードで使用するときのTips

# はじめに

Nuxt.jsのストアは、「クラシックモード」と「モジュールモード」の2つの書き方があります。

中〜大規模なアプリケーションでストアが肥大化してくると、
クラシックモードでは見通しが悪くなるので、モジュールモードで管理したほうがスッキリ見やすくなりよいとされています。
 
今回は、クラシックモードからモジュールモードに書き換える際に便利だった点・ハマった点を残しておきます。

# 想定する読者
- Vuexを使ったことがあり、ミューテーションなどの基本的な使用法はわかる
- Nuxt.jsでストアを使用している
- Nuxt.jsでストアのモジュールモードを使用した開発をしている、またはしようと思っている

# ストアの構成
### クラシックモードで書くと

まず普通にクラシックモードで書いた場合です。

```js:store/index.js
import Vuex from 'vuex'

const state = () => ({
  counter: 0,
  isSignedIn: false
})

const mutations = {
  increment(state){
    state.counter++
  },
  setSignInState(state, isSignedIn){
    state.isSignedIn = isSignedIn
  }
}

const actions = {
  async signIn({ commit }){
    // ログイン処理(API呼び出しなど)
    const isSignedIn = await ...
    commit('setSignInState', isSignedIn)
  },
  signOut({ commit }){
    commit('setSignInState', false)
  }
}

const store = () => new Store({
  state,
  mutations,
  actions
})

export default store
```

### モジュールモードで書くと

上のストアをモジュールに分けてみます。

```js:store/index.js
export const state = () => ({
  counter: 0
})

export const mutations = {
  increment(state){
    state.counter++
  }
}
```

```js:store/user.js
export const state = () => ({
  isSignedIn: false
})

export const mutations = {
  setSignInState(state, isSignedIn){
    state.isSignedIn = isSignedIn
  }
}

export const actions = {
  async signIn({ commit }){
    // ログイン処理
    const isSignedIn = await ...
    commit('setSignInState', isSignedIn)
  },
  signOut({ commit }){
    commit('setSignInState', false)
  }
}
```

### ポイント

Nuxtでは、次のいずれかの場合、モジュールモードで動作するようになります。

- `index.js`がストアオブジェクトをexportしない
- または`index.js`が存在しない

store配下に置いたjsファイルが、ファイル名の名前をもつモジュールとして自動で登録されます。
index.jsに定義したものはグローバルに登録されます。

もちろん、クラシックモードでもストアをモジュールに分割することは可能ですが、
その場合`namespaced`オプションの記述忘れなどがあると正しく登録できないため、
モジュールモードを利用するほうが安全です。

モジュール分割されたストアの値、メソッドへのアクセス方法は以下のようになります。

```html:pages/index.vue
<template>
  <div>

    <template v-if="$store.state.user.isSignedIn">
      <p>ログインしています</p>
      <button @click="$store.dispatch('user/signOut')">ログアウト</button>
    </template>

    <template v-else>
      <p>ログインしてください</p>
      <button @click="$store.dispatch('user/signIn')">ログイン</button>
    </template>

    <p>カウンター: {{ $store.state.counter }}</p>
    <button @click="$store.commit('increment')">カウントする</button>

  </div>
</template>
```

# ヘルパー関数の使い方

Vuexには、便利な「ヘルパー関数」があり、
コンポーネントのデータ・メソッドへのマッピングを簡潔に行うことができます。

|要素|ヘルパー関数
|:--|:--
|ステート|mapState
|ゲッター|mapGetters
|ミューテーション|mapMutations
|アクション|mapActions

参考: [Vuex - 名前空間によるバインディングヘルパー](https://vuex.vuejs.org/ja/guide/modules.html#%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93%E3%81%AB%E3%82%88%E3%82%8B%E3%83%90%E3%82%A4%E3%83%B3%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E3%83%98%E3%83%AB%E3%83%91%E3%83%BC)

## ヘルパー関数を使ってバインディングする

これらを利用してバインディングを行うとこうなります。
スプレッド演算子を使用することで、既存のプロパティがある場合もそれらとマージできます。

```html:pages/index.vue
<template>
  <div>

    <template v-if="isSignedIn">
      <p>ログインしています</p>
      <button @click="this['user/SignOut']()">ログアウト</button>
    </template>

    <template v-else>
      <p>ログインしてください</p>
      <button @click="this['user/SignIn']()">ログイン</button>
    </template>

    <p>カウンター: {{ counter }}</p>
    <button @click="increment">カウントする</button>

  </div>
</template>

<script>
import { mapState, mapMutations, mapActions } from 'vuex'

export default {
  computed: {
    ...mapState({
      counter: state => state.counter,
      isSignedIn: state => state.user.isSignedIn
    })
  },
  methods: {
    ...mapMutations([
      'increment'
    ]),
    ...mapActions([
      'user/SignIn',
      'user/SignOut'
    ])
  }
}
</script>
```

...なんだか冗長な感じがします。
ここからがヘルパー関数の本領発揮です。

## より簡潔なバインディング法

```html:pages/index.vue
<template>
  <div>

    <template v-if="isSignedIn">
      <p>ログインしています</p>
      <button @click="signOut">ログアウト</button>
    </template>

    <template v-else>
      <p>ログインしてください</p>
      <button @click="signIn">ログイン</button>
    </template>

    <p>カウンター: {{ counter }}</p>
    <button @click="increment">カウントする</button>

  </div>
</template>

<script>
import { mapState, mapMutations, mapActions } from 'vuex'

export default {
  computed: {
    ...mapState(['increment']),
    ...mapState('user', [
      'isSignedIn'
    ])
  },
  methods: {
    ...mapMutations([
      'increment'
    ]),
    ...mapActions('user', [
      'signIn',
      'signOut'
    ])
  }
}
</script>
```

だいぶスッキリしました。

ヘルパー関数の第一引数に`「モジュール名」`、
第二引数に`「プロパティ名 / メソッド名」`を渡してあげることで、
モジュールや複数の値をバインディングしたい場合に楽です。

## バリエーション

ヘルパー関数の使い方は3種類あります。
それぞれ、各ヘルパー関数(mapState, mapGetters, mapMutations, mapActinos)すべてで使用できます。

### 1. 基本

```js
mapState([
  'increment',
  'user/isSignedIn'
])
```
モジュールの場合は、`モジュール名/プロパティ名`とします。

### 2. モジュール名を渡す

```js
mapState('user', [
  'isSignedIn'
])
```

ひとつのモジュールに対して、複数のプロパティをバインドしたい場合に便利です。

### 3. 第二引数にオブジェクトを渡す

```js
mapState('user', {
  isLoggedIn: 'isSignedIn'
})
```

プロパティに別名をつけたい場合に使います。
コンポーネントで既に同じ名前のプロパティがある場合などに便利ですね。

# vuex-persistedstateでの書き方

ストアの状態をローカルストレージに保存する際、`vuex-persistedstate`プラグインを使用します。

ここで少しハマったのですが、`paths`の記述方法は
`モジュール名/プロパティ名` ではなく `モジュール名.プロパティ名` のようです。

```js:plugins/localStorage.js
import createPersistedState from 'vuex-persistedstate'

export default ({ store }) => {
  createPersistedState({
    key: 'myApp',
    paths: [
      'increment',
      'user.isSignedIn'
    ]
  })(store)
}
```

`モジュール名/プロパティ名`で書いたところ、
ローカルストレージに`mpApp`オブジェクトは作成されるものの、
中身が一向に入ってこないので、おかしいなぁと試してみたところ、`.(ドット)`でいけました。

# おわりに

各ヘルパー関数の使い方のバリエーションや、
vuex-persistedstateでのモジュールの記述方法など、
公式ドキュメントに載っていない・よく見ないとわかりにくい~~読むのがめんどくさい~~ので
まとめてみました。
（vuex-persistedstateのほうは載っていないと思いましたが、自分の探し方が甘かったのでしょうか...？）

ご指摘・追加情報等ありましたら、コメント等いただけますと幸いです。