Vuexの使い方や責務について
Vuexとは、状態管理のライブラリです。Vuexにおける状態(state)とは、フロントが保持するデータのことです。それらのデータは複数のコンポーネントから参照することが出来るため、propsや$emitを使ったバケツリレーを回避してくれます。
データの流れ
親から孫(孫から親)コンポーネントに、propsや$emitを使ってバケツリレーでデータをやり取りする図です。
こちらはVuexを使って、親から孫コンポーネントにデータをやり取りする図です。バケツリレーを回避しています。
そして次はVuex公式ページでよく見る図です。基本的なデータの流れは Actions → Mutations → State → Vue Components となります。しかし、Vue ComponentsはStateを直接参照せずGetterを経由することが推奨されています。
Stateを直接参照しないことに関する説明は、こちらのサイトが分かりやすいです。
役割
VuexのStoreは、Actions, Mutations, Getter, Stateの4つの要素から構成されています。
State
- データの入れ物です
- 各種APIレスポンスをそのまま保持します
- ユーザーのログイン情報など、複数コンポーネントを跨ぎたい情報を管理します
Actions
- Mutationsを介して、Stateを更新するメソッドです
- 非同期処理でなければなりません
Mutations
- Stateを更新するメソッドです
- 同期処理でなければなりません
Getters
- Stateの内容から算出される値です
- Componentにデータを加工して提供します(Viewに表示させる)
それぞれ役割については以上です。
Stateを更新して良いのはMutationsのみで、Componentにデータを渡すのはGettersのみと制約がはっきりしています。
GettersをViewに依存させない
先ほどGettersの役割は、Componentにデータを加工して提供すると書きました。しかし、表示用の加工処理をGettersに任せてしまうと、Storeの処理が肥大化し管理が難しくなります。
そのため、StoreからGettersで取得した値をComputedなどで処理をして表示するようにします。
Vuexの責務
Vuexを使うとバケツリレーの回避ができますが、目的をバケツリレーの回避(propsや$emitのショートカット)にするのはアンチパターンと言われています。
アンチパターンの理由は、どこで何が起きているのかを追うのが難しくなり、予期せぬ所からデータが更新されてしまったり、テストが難しくなったりと不具合を起こす要因になるためです。
Vuexに入れるべきデータは慎重に検討するべきです。対策として例えば、Atomic Designを導入している場合は、PagesやTemplatesなどの大きな単位に限ったり、コンポーネントに閉じれるデータは、そのコンポーネント内で管理するなどです。
Vuexを使う場合は、propsや$emitによるバケツリレーの回避を目的とするのではなく、データの流れを一方向にし、グローバルなデータを安全に一元管理にすることに意識を向ける事で、Vuexがよりセーフティになります。
サンプルコード
次のコードはstoreを直接参照していますが、とりあえず動かしてみたい人向けの簡単なサンプルコードです。
1.storeを使ってHello Vuex!!!を出力
% vue init webpack my-project
で作成したプロジェクトにVuexをインストールします。そしてstoreを使って、画面上に「Hello Vuex」を表示します。
App.vue
<template>
<div id="app">
<h1>{{ $store.state.message }}</h1>
</div>
</template>
main.js
import Vuex from 'vuex'
import Vue from 'vue'
import App from './App'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
message: 'Hello Vuex!!!',
},
})
new Vue({
el: '#app',
template: '<App/>',
components: { App },
store,
})
2.Mutationsを使ってカウントアップ
次はMutationsを使い、UPボタンをクリックで数がカウントアップするコードです。
<template>
<div id="app">
<p><button @click="increment">UP</button>
<h1>Count:{{ this.$store.state.count }}</h1>
</div>
</template>
<script>
export default {
name: 'app',
methods: {
increment() {
this.$store.commit('increment')
}
}
}
</script>
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
}
})
3.2件ずつカウントアップ
Commitには値を渡すことができ、これをpayloadと呼びます。先ほどはボタンクリックで1つ値が増えましたが、一度に2増ずつやしたい場合は、数値の2をpayloadに設定します。そうすることで、VuexのMutationsに設定した値を渡せます。
methods: {
increment() {
this.$store.commit('increment', 2)
}
}
mutations: {
increment(state, payload) {
state.count = state.count + payload
}
}
4.Actionsを使って、カウントアップ
Vuexでは通常のvue.jsのMethodsに対応するActionsがあります。ActionsはStateを直接変更するのではなく、Mutationsを経由してStateを更新します。
methods: {
increment() {
this.$store.dispatch('incrementActions')
}
}
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementActions(context) {
context.commit('increment')
}
}
})
参考
Discussion