Vuexを使っていてstateが多くなってきたのでモジュール化したいと思い、せっかくなので各モジュールを1ファイル毎に分けてみました。
モジュール化の設定と、コンポーネントからの利用方法をメモしておきます。
全部公式ページに書いてあることですが、特に呼び出し方なんかは、毎回「どう書くんだったけ??」と探すのが面倒だったのでまとめてみました。
環境
- vue: 2.6
- vuex: 3.5
ファイルを分ける
さっそく各モジュールを1ファイに分けていきます。
今回はVuexの公式サイトの例をベースに利用したいと思います。
構成
ディレクトリ構成はこんな感じにします。
store
├── index.js
├── modules
│ ├── moduleA.js
│ └── moduleB.js
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の値も変わってしまいます。(参照渡しになるので)
const getDefaultState = {
message: '',
count: 0,
}
export default {
namespaced: true,
state: getDefaultState,
//・・・・・
actionIncrement({commit}) {
actionは引数にコンテキストオブジェクトを受け取るが、この例ではcommitしか使わないので引数分割の記法を使っています。
以下と同じこと。
actionIncrement(context) {
context.commit('increment')
},
コンテキストオブジェクトには、他にも state
getters
dispatch
があるので利用したいものを複数指定するのもあり。
こんな感じ actionIncrement({state, commit}) {
moduleB.jsファイルを作る
moduleA.jsと同じ感じなので省略。
モジュールファイルをstoreに登録する
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 },
モジュールファイルを登録します。
オブジェクトキー省略記法を使っていますが、下記と同じことです。
modules: {
moduleA: moduleA,
moduleB: moduleB
},
VuexをVueに登録
あとはindex.jsをインポートしてVueオブジェクトを作るだけ。
Vue.use(Vuex)
new Vue({
store,
}).$mount('#app')
moduleを利用する
モジュール分割して namespaced: true
にしている前提の呼び出し方です。
VueのComponentで利用する
stateの参照
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.moduleA.count
}
}
}
形式は以下の通り。
store.state.【modulesオブジェクトのキー】.【stateの値】
※modulesオブジェクトは先ほど上で書いたやつです。
mutationを使う
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の第三引数と第四引数にルート参照できる変数がそれぞれ渡されるので、そこからアクセスする。
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
の引数を増やすことで対応する。
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リファレンス
commit
も同様ですね。
ヘルパー関数の場合
ヘルパー関数の場合は第一引数にnamespaceを指定する。 → APIリファレンス
import { mapState } from 'vuex'
export default {
computed: mapState('moduleA', {
count: state => `${state.count}回`,
})
}