【Nuxt 2】Vuex(Store)
Vuex とは
Vuex は、Vue.js アプリケーションにおいて「状態」を管理するのためのライブラリです。 この「状態」は、コンポーネントとは別で管理され、すべてのコンポーネントから参照・更新が可能です。
例えば SPA を構築する場合に、すべてのページでユーザー情報が必要だとします。
基本的にページ毎に状態(データ)を持つため、ページ遷移の度に状態はリセットされます。 つまりページ遷移の度に、何かしらの手段を用いてユーザー情報を取得する必要があります。
これを Vuex によって管理するようにします。 前述したように、Vuex はコンポーネントとは別で管理されます。 つまり、ページを遷移したとしても Vuex で管理している状態はそのまま保持されているため、ユーザー情報の取得は必要最低限で済みます。
![](/img/post/nuxt/nuxt-vuex/vuex1.png)
Vuex の状態はメモリ上で管理されるため、ウィンドウを閉じたり、ページリロードをすると初期化される点に注意が必要です。
Vuex の基本
Vuex では、以下の図のように状態管理が行われます。 以下よりそれぞれの役割について説明します。
![](https://vuex.vuejs.org/vuex.png)
State
State は、そのまま「状態」を表します。 先程の例でいうユーザー情報など、すべてのコンポーネントで共通的に管理したいデータを設定します。
コンポーネントは、State を参照することができますが、直接更新することはできません。 更新する場合は、以下で説明する Mutations または Actions を使用します。
Mutations
Mutations は、State を更新するためのものです。 言い換えると、State を更新できるのは Mutations のみです。
Mutations は、Commit という形で呼び出され、State を更新する処理を行います。 上図では Actions からのみ呼び出されていますが、コンポーネントから直接呼び出すこともあります。
注意点として、Mutations は Commit 時に渡されるデータを基に State を更新するだけであり、 Mutations が自ら更新用のデータを取得することは行いません。 データを取得する役割を担うのは、Actions またはコンポーネントです。
Actions
上述したように、Actions は API などを介して外部からデータを取得するためのものです。 取得したデータを Mutations に渡すことで State を更新します。 Actions が State を直接更新することはできません。
Actions は、Dispatch という形でコンポーネントから呼び出されます。
コンポーネント自身が外部からデータを取得し、Mutations を呼び出しても良いのですが、 処理の共通化という観点で見たときに、Actions を利用したほうが良いケースは多いはずです。 これについてはケースバイケースで判断してください。
Getters
上図には記載がありませんが、Getters というものがあります。 これは、State を加工した状態で参照する場合に使用します。 例えば State で管理している日付を「yyyy/MM/dd」にフォーマットされた状態で参照したい、というように使用します。
Vuex の利用
定義
NuxtJS にはデフォルトで Vuex が搭載されています。
利用するためには、store
フォルダを作成し、index.js
ファイルを作成します。
以下はユーザー情報を管理するための簡単な例になります。
import axios from 'axios'
export const state = () => ({
user: null,
})
export const getters = () => {
userName: state => {
return state.user?.name ?? 'No Name';
}
}
export const mutations = {
set(state, user) {
state.user = user
},
clear(state) {
state.user = null
},
}
export const actions = {
async fetch(context, id) {
const { data } = await axios.get(`/api/user/${id}`)
context.commit('set', data)
},
async update(context, user) {
await axios.put(`/api/user/${user.id}`, user)
context.commit('set', user)
}
}
以下は、定義に関する補足事項です。
Mutations、Actions へは、データは1つにまとめて渡す
Mutations と Actions で定義するメソッドでは、引数が決まっています。
第一引数は、各々で決まったオブジェクト(Mutaions はstate
、Actions はcontext
)となります。
この設定は、必然的に必須となります。
第二引数は、処理に必要なすべてのデータとなります。
つまり、Mutations や Actions を呼び出す際は、必要なデータをすべて 1 つのオブジェクト(または値)として渡す必要があります。
ちなみに第三引数以降は無視されます(undefined
になります)。
具体的に、id
とname
をデータとして渡したい場合は、{ id, name }
のようにまとめる必要があるということです。
当然メソッドは、user.id
、user.name
のように参照する必要があります。
Actions の context は、Vuex の情報
Actions の第二引数であるcontext
は、以下 Vuex の情報を持ちます。
state
:自身の Stategetters
:自身の Getterscommit
: Mutations の実行dispatch
:Actions の実行rootState
:すべての StaterootGetters
:すべての Getters
今回は、State の更新のために Mutasions のset
を実行したいので、context.commit('set', user)
としています。
上記では説明のためcontext
としていますが、以下のように必要なものだけを参照する方が一般的です。
async fetch({ commit }, id) {
const { data } = await axios.get(`/api/user/${id}`)
commit('set', data)
},
state
とrootState
の違いについては後述します。
参照
コンポーネントで Vuex の情報を参照するには、this.$store
とします。
export default {
computed: {
user() {
return this.$store.state.user
},
userName() {
return this.$store.getters.userName;
}
},
methods: {
async fetchUser() {
await this.$store.dispatch('fetch', this.user.id)
},
asnyc updateUser() {
await this.$store.dispatch('update', this.user)
},
asnyc updateUser()
clearUser() {
this.$store.commit('clear')
}
}
}
Mutations の処理はcommit('メソッド名')
、Actions の処理はdispatch('メソッド名')
として実行します。
middleware
やasnycData
などの場合は、context
オブジェクトのstore
によって参照します。
export default function({ store }) {
//...
}
モジュール分割
上記ではindex.js
を作成して Vuex の定義を行いました。
しかし、管理する状態が増える程 1 ファイルに定義していくのは大変になります。
そこで、状態ごとにファイル分けることを考えます。
NuxtJS であれば、store
フォルダに状態名の JS ファイルを作成するだけです。
例として、user
とtodo
という状態をファイルを分けて作成します。
export const state = () => ({
id: '',
name: ''
})
export const mutations = {
set(state, user) {
state = user
}
}
export const actions = {
fetch({ commit }, id) {
const { data } = await axios.get(`/api/user/${id}`)
context.commit('set', data)
}
}
export const state = () => ({
list: []
})
export const getters = {
count: state => {
return state.list.length
}
}
export const mutations = {
add(state, todo) {
state.list.add(todo)
}
}
以下はそれぞれの参照例です。
index.js
として定義した場合と異なり、各状態名(ファイル名)が必要になる点に注意してください。
//State
this.$store.state.user.id
this.$store.state.todo.list
//Getters
this.$store.getters['todo/count']
//Mutations
this.$store.commit('user/set', this.user)
this.$store.commit('todo/add', this.todo)
//Actions
this.$store.dispatch('user/fetch', this.id)
context に関する補足
説明を保留していた、state
とrootState
の違いについてです。
state
は自身の State であり、rootState
はすべての State になります。
例えば上記のuser
の場合、state
としてはid
とname
が参照できます。
rootState
を用いれば、rootState.todo.list
のようにすべての状態が参照できます。
getters
、rootGetters
についても同様です。
Map ヘルパー
Vuex は、上述したようにstore
によって参照します。
しかし、これを 1 つ 1 つコンポーネントのcomputed
やmethods
に割り当てるのは面倒です。
そこで役に立つのが Map ヘルパーです。 各役割ごとに Map ヘルパーが用意されており、以下のようにコンポーネントへの割り当てを簡易的に行うことができます。
mapState
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
user: (state) => state.user, //this.user
todos: (state) => state.todo.list, //this.todos
}),
},
}
mapGetters
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters({
count: 'todo/count', //this.count
}),
},
}
mapMutations
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations({
setUser: 'user/set', //this.setUser(user)
addTodo: 'todo/add', //this.addTodo(todo)
}),
},
}
mapActions
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions({
fetchUser: 'user/fetch', //this.fetchUser(id)
}),
},
}