290
245

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2018-12-02

はじめに

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

中〜大規模なアプリケーションでストアが肥大化してくると、
クラシックモードでは見通しが悪くなるので、モジュールモードで管理したほうがスッキリ見やすくなりよいとされています。

今回は、クラシックモードからモジュールモードに書き換える際に便利だった点・ハマった点を残しておきます。

想定する読者

  • Vuexを使ったことがあり、ミューテーションなどの基本的な使用法はわかる
  • Nuxt.jsでストアを使用している
  • Nuxt.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

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

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

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

export const mutations = {
  increment(state){
    state.counter++
  }
}
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オプションの記述忘れなどがあると正しく登録できないため、
モジュールモードを利用するほうが安全です。

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

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 - 名前空間によるバインディングヘルパー

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

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

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>

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

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

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. 基本

mapState([
  'increment',
  'user/isSignedIn'
])

モジュールの場合は、モジュール名/プロパティ名とします。

2. モジュール名を渡す

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

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

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

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

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

vuex-persistedstateでの書き方

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

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

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

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

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

おわりに

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

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

290
245
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
290
245

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?