はじめに
Nuxt.jsのストアは、「クラシックモード」と「モジュールモード」の2つの書き方があります。
中〜大規模なアプリケーションでストアが肥大化してくると、
クラシックモードでは見通しが悪くなるので、モジュールモードで管理したほうがスッキリ見やすくなりよいとされています。
今回は、クラシックモードからモジュールモードに書き換える際に便利だった点・ハマった点を残しておきます。
想定する読者
- Vuexを使ったことがあり、ミューテーションなどの基本的な使用法はわかる
- Nuxt.jsでストアを使用している
- Nuxt.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
モジュールモードで書くと
上のストアをモジュールに分けてみます。
export const state = () => ({
counter: 0
})
export const mutations = {
increment(state){
state.counter++
}
}
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
オプションの記述忘れなどがあると正しく登録できないため、
モジュールモードを利用するほうが安全です。
モジュール分割されたストアの値、メソッドへのアクセス方法は以下のようになります。
<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 |
ヘルパー関数を使ってバインディングする
これらを利用してバインディングを行うとこうなります。
スプレッド演算子を使用することで、既存のプロパティがある場合もそれらとマージできます。
<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>
...なんだか冗長な感じがします。
ここからがヘルパー関数の本領発揮です。
より簡潔なバインディング法
<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. 基本
mapState([
'increment',
'user/isSignedIn'
])
モジュールの場合は、モジュール名/プロパティ名
とします。
2. モジュール名を渡す
mapState('user', [
'isSignedIn'
])
ひとつのモジュールに対して、複数のプロパティをバインドしたい場合に便利です。
3. 第二引数にオブジェクトを渡す
mapState('user', {
isLoggedIn: 'isSignedIn'
})
プロパティに別名をつけたい場合に使います。
コンポーネントで既に同じ名前のプロパティがある場合などに便利ですね。
vuex-persistedstateでの書き方
ストアの状態をローカルストレージに保存する際、vuex-persistedstate
プラグインを使用します。
ここで少しハマったのですが、paths
の記述方法は
モジュール名/プロパティ名
ではなく モジュール名.プロパティ名
のようです。
import createPersistedState from 'vuex-persistedstate'
export default ({ store }) => {
createPersistedState({
key: 'myApp',
paths: [
'increment',
'user.isSignedIn'
]
})(store)
}
モジュール名/プロパティ名
で書いたところ、
ローカルストレージにmpApp
オブジェクトは作成されるものの、
中身が一向に入ってこないので、おかしいなぁと試してみたところ、.(ドット)
でいけました。
おわりに
各ヘルパー関数の使い方のバリエーションや、
vuex-persistedstateでのモジュールの記述方法など、
公式ドキュメントに載っていない・よく見ないとわかりにくい読むのがめんどくさいので
まとめてみました。
(vuex-persistedstateのほうは載っていないと思いましたが、自分の探し方が甘かったのでしょうか...?)
ご指摘・追加情報等ありましたら、コメント等いただけますと幸いです。