Vuexでモジュール分割　〜ファイルを分けてみた〜


Vuexを使っていてstateが多くなってきたのでモジュール化したいと思い、せっかくなので各モジュールを1ファイル毎に分けてみました。

モジュール化の設定と、コンポーネントからの利用方法をメモしておきます。
全部公式ページに書いてあることですが、特に呼び出し方なんかは、毎回「どう書くんだったけ？？」と探すのが面倒だったのでまとめてみました。


# 環境

- vue: 2.6
- vuex: 3.5

# ファイルを分ける

さっそく各モジュールを1ファイに分けていきます。
今回はVuexの[公式サイトの例](https://vuex.vuejs.org/ja/guide/modules.html)をベースに利用したいと思います。

## 構成

ディレクトリ構成はこんな感じにします。

```
store
├── index.js
├── modules
│   ├── moduleA.js
│   └── moduleB.js
```

## moduleA.jsファイルを作る

```javascript:moduleA.js
const getDefaultState = () => ({
  message: '',
  count: 0,
})

export default {
  namespaced: true,
  state: getDefaultState(),
  mutations: {
    message(state, value) {
      state.message = value
    },
    increment(state) {
      state.count++
    },
    reset(state) {
      Object.assign(state, getDefaultState())
    },
  },
  getters: {
    isEven: (state) => state.count % 2 === 0,
  },
  actions: {
    actionIncrement({commit}) {
      commit('increment')
    },
  },
}
```

### 説明

#### `namespaced: true,`

これを指定して名前空間を分けます。
もし`moduleB.js`に同じ名前のmutationやgetterがあった場合でも、名前空間が違うので被ることはありません。（指定しないと、どちらかの定義が上書きされるのかな？？）


#### `state: getDefaultState(),`

stateの初期値を返却する関数を作ることでstateのリセットをしやすくしてみました。
Vue.jsではオブジェクト内の一部を変更してもリアクティブに伝搬してくれないので、`Object.assign(state, getDefaultState())`でstate全体を変更することで対応しています。

ちなみに、getDefaultStateを関数ではなく変数にしてしまうと、コンポーネント側で色々とstateを変えたりしているとgetDefaultStateの値も変わってしまいます。（参照渡しになるので）

```javascript:これはダメ

const getDefaultState = {
  message: '',
  count: 0,
}

export default {
  namespaced: true,
  state: getDefaultState,
  //・・・・・
```


#### `actionIncrement({commit}) {`

actionは引数にコンテキストオブジェクトを受け取るが、この例ではcommitしか使わないので引数分割の記法を使っています。
以下と同じこと。

```javascript
    actionIncrement(context) {
      context.commit('increment')
    },
```

コンテキストオブジェクトには、他にも `state` `getters` `dispatch` があるので利用したいものを複数指定するのもあり。 
こんな感じ `actionIncrement({state, commit}) {` 


## moduleB.jsファイルを作る

moduleA.jsと同じ感じなので省略。

## モジュールファイルをstoreに登録する

```javascript:index.js
import Vuex from 'vuex'
import moduleA from '@/store/modules/moduleAjs'
import moduleB from '@/store/modules/moduleB.js'

export default new Vuex.Store({
  modules: { moduleA, moduleB },
})
```

### 説明

#### `modules: { moduleA, moduleB },`

モジュールファイルを登録します。
オブジェクトキー省略記法を使っていますが、下記と同じことです。

```javascript

  modules: {
     moduleA: moduleA,
     moduleB: moduleB
  },
```

## VuexをVueに登録

あとはindex.jsをインポートしてVueオブジェクトを作るだけ。

```javascript

Vue.use(Vuex)

new Vue({
  store,
}).$mount('#app')
```

# moduleを利用する

モジュール分割して `namespaced: true`にしている前提の呼び出し方です。


## VueのComponentで利用する

### stateの参照

```javascript

const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.moduleA.count
    }
  }
}
```

形式は以下の通り。

```
store.state.【modulesオブジェクトのキー】.【stateの値】
```

※modulesオブジェクトは[先ほど上で書いたやつ](#モジュールファイルをstoreに登録する)です。


### mutationを使う

```javascript

  methods: {
    setMessage(text) {
      this.$store.commit('moduleA/message', text)
    },
  },
```

形式は以下の通り。

```
store.commit('【modulesオブジェクトのキー】/【mutation名】', 【mutationの引数】)
```

mutationの引数が要らない場合は第一引数のみでOK。

### その他

#### action

```
store.dispatch('【modulesオブジェクトのキー】/【action名】', 【actionの引数】)
```


#### getter

```
store.getters['modulesオブジェクトのキー】/【getter名】']
```
moduleAの例で言うと `this.$store.getters['moduleA/isEven']`


## 他のモジュールファイル内から利用する

今回の例で言うと、moduleB.js内でmoduleA.jsのstateやactionを使いたい場合。

### getter内で利用する場合

getterの第三引数と第四引数にルート参照できる変数がそれぞれ渡されるので、そこからアクセスする。

```javascript:moduleB.js
getters: {
  someGetter(state, getters, rootState, rootGetters) {
    //moduleAのstateを参照する
    const count = rootState.moduleA.count
    //moduleAのgetterを使う
    const isEven = rootGetters['moduleA/isEven'] 
  },
},
```

### action内で利用する場合

stateとgettterは、引数のコンテキストオブジェクト内にルート参照できる変数が入っているので、そこからアクセスする。
actionとmutationは、`dispatch` `commit` の引数を増やすことで対応する。

```javascript:moduleB.js
actions: {
  someAction({ dispatch, commit, rootState, rootGetters }) {
    //moduleAのstateを参照する
    const count = rootState.moduleA.count
    //moduleAのgetterを使う
    const isEven = rootGetters['moduleA/isEven'] 
    //moduleAのactionを使う
    dispatch('moduleA/actionIncrement', null, { root: true })
    //moduleAのmutationを使う
    commit('moduleA/message', 'メッセージだよ', { root: true })
  },
},
```

`dispatch`の第二引数はactionには渡したい値を入れます。
第三引数の`{ root: true }` でstoreのルートから辿る様になり、 第一引数の`'moduleA/actionIncrement'` でモジュール指定できます。　→　[APIリファレンス](https://vuex.vuejs.org/ja/api/#dispatch)

`commit`も同様ですね。

## ヘルパー関数の場合

ヘルパー関数の場合は第一引数にnamespaceを指定する。 →　[APIリファレンス](https://vuex.vuejs.org/ja/api/#%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%82%92%E3%83%90%E3%82%A4%E3%83%B3%E3%83%89%E3%81%99%E3%82%8B%E3%83%98%E3%83%AB%E3%83%91%E3%83%BC)

```javascript
import { mapState } from 'vuex'

export default {
  computed: mapState('moduleA', {
    count: state => `${state.count}回`,
  })
}
```


# 参考

[Vuex公式（日本語）](https://vuex.vuejs.org/ja/)