Vuexの概念が何となく理解出来たので、ついでに使い方もまとめておく。
自社の開発においても大量のコンポーネントを扱わなければならない中でVuexが必須となっているので、勉強し直した。
Vuex(ビューエックス)とは何か?
ググったら一番最初に出てくるこれを見てもイマイチ分からない人に向け、
vue.jsの基本は一応分かっている前提で説明すると、
沢山のコンポーネントがあるそれなりの規模のプロジェクトがあったとする。
さて、この図でComponentAのデータをComponentCやComponentEに渡すにはどうしたらいいだろうか。
親コンポーネントから子コンポーネントへ、
また子コンポーネントから親コンポーネントへのデータのやりとりが出来るのはわかると思う。でも、孫から子へ、それを親に渡し、さらにそれを子から孫へ、みたいなやりとりは手間である。($emitして$emitしてpropsしてprops
して見たいにやるのはスマートではない。)
そこでVuexを使うと、グローバル変数のような感じでデータを扱うことが出来るようになると言う感じ(概念)。と言われれば分かり易いのではないだろうか。これで深いコンポーネント同士でのやりとりが出来るようになる。
導入
Vuexのパッケージをインストールする。
vue cliでプロジェクト作成時にセットでインストール済みである場合は必要ない。
npm install vuex
vuex用のファイルを新たに設ける。
ファイル名は何でも良いのだが、store.js
とするのが一般的。
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex); // vuexをvue全体で使用する宣言
export default new Vuex.Store({ // main.jsで読み込めるようにする
// 以下で定義したものはどのコンポーネントでも使用出来る
state: {
number: 2
}
})
あとはmain.js
で読み込むだけ。
import Vue from 'vue';
import App from './App';
import router from './router';
import store from './store' // store.jsをインポート
Vue.config.productionTip = false;
new Vue({
el: '#app',
router,
store, // store: store,
components: { App },
template: '<App/>',
});
stateを使ってみる
文字通りstoreの状態
をプロパティーとして返すと言う位置付けになろうか。前述の通り、ここで定義した内容はグローバルに取得する事が出来るようになる。
export default new Vuex.Store({ // main.jsで読み込めるようにする
// 以下で定義したものはどのコンポーネントでも使用出来る
state: {
number: 2
}
})
ComponentAで表示する変数を
<template>
<p>{{ number }}</p>
</template>
<script>
export default {
computed: {
number() {
// $storeとする事でどこからでも呼べるようになる
return this.$store.state.number;
}
}
}
</script>
ComponentEから操作出来る、みたいな事が出来る。
<template>
<b-button @click="increment">+1</b-button>
</template>
<script>
export default {
methods: {
increment(){
// $storeとする事でどこからでも呼べるようになる
this.$store.state.number++;
},
},
}
</script>
gettersを使ってみる
storeの状態
を算出したい時にgetters
を定義する事で、算出プロパティーとして使う事が出来る感じ。
export default new Vuex.Store({ // インスタンスをmain.jsで読み込めるようにする
state: { // 状態を全体で使用出来るようにする
number: 0
},
getters: {
counter: state => state.number++,
}
})
こんな感じでVuexの中でcomputed
のような事が出来る。
<template>
<p>{{ count }}</p>
</template>
<script>
export default {
computed: {
count() {
return this.$store.getters.counter; // $store.gettersで呼ぶ
}
}
}
</script>
まぁこの程度であればgettersを使う意味はないのだけど、
よりコンポーネントが複雑化してきた時に真価を発揮するものなのでそこはご容赦頂きたい。
複数のgettersを扱う時にもっと便利に使う事が出来るのが、mapGetters
である。
export default new Vuex.Store({
state: {
number: 2
},
// 複数定義しているものがあるとする
getters: {
doubleCount: state => state.number * 2,
tripleCount: state => state.number * 3
}
})
こう書くことも出来るが、
<script>
export default {
computed: {
doubleCount() {
return this.$store.getters.doubleCount;
},
tripleCount() {
return this.$store.getters.tripleCount;
},
}
}
</script>
mapGetters
を用いると一気に整理される。
<script>
import { mapGetters } from "vuex"; // mapGettersをインポートする
export default {
// 配列
computed: mapGetters(["doubleCount", "tripleCount"]),
}
</script>
配列ではなく、オブジェクトでも良い。
<script>
import { mapGetters } from "vuex"; // mapGettersをインポートする
export default {
// オブジェクトでも良い
computed: mapGetters({
doubleCount: "doubleCount",
tripleCount: "tripleCount"
}),
}
</script>
必要なgettersだけを取ってくる事が出来るので是非使いこなしたい。
ちなみにこのままだと、computedにmapGetters以外に記述する事が出来ないので、普段使いでは
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
// スプレッド演算子(object spread operator)を使って組み込む
...mapGetters([
'doubleCount',
'tripleCount',
])
}
}
</script>
という感じで使うっぽい。
mutationsを使ってみる
stateを使う事でコンポーネント間のデータのやりとりは簡単にはなるけれど、
何でもかんでも自由にやりとりをさせ過ぎると、逆に分かりづらくなったり、追跡、管理が煩雑になる。そこで使用するのがmutationsという事のようだ。
export default new Vuex.Store({
state: {
number: 2
},
mutations: {
increment(state, str) { // 第一引数にstateをとり、実際の変更を記述する
state.number += str;
}
}
})
呼び出す側ではstore.commitでmutationsを指定する。
<script>
methods: {
increment(){
this.$store.commit('increment', 2);
},
},
<script>
stateをあっちこっちで変更する事は思わぬバグを発生させる可能性があるから、
mutationsで扱いましょうという感じだろうか。
また、mutationsにもmapMutations
というヘルパーが存在する。
<script>
import { mapMutations } from "vuex"; // mapMutationsをインポートする
export default {
methods: {
// mapGettersと同じようにスプレット演算子で書ける
...mapMutations(["increment","decrement"]),
},
}
</script>
ちなみにmutationsは同期的でなければならないというルールがある。
では非同期を扱うにはどうしたらいいのか。
actionを使ってみる
actionはmutationsと異なり、任意の非同期処理を含む事が出来る。
なので、setTimeoutで一定時間後に特定の処理を行うという記述も可能となっている。
export default new Vuex.Store({
state: {
count: 2
},
actions: {
increment({ commit },number) {
commit('increment', number);
}
}
})
store内のactionsでcommitし、コンポーネントでdispatchする。
<script>
methods: {
increment() {
this.$store.dispatch('increment', 2);
}
},
</script>
非同期でもOK
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
最後はmapActions
ヘルパー、これまで出てきたmapと同じ感覚で使用出来る。
<script>
import { mapActions } from "vuex";
export default {
methods: {
// スプレット演算子で書ける
...mapActions(["increment","decrement"]),
}
</script>
まとめると・・・
・stateでvuexの状態を管理
・gettersでstateの変更を算出
・mutationsでstateの状態を変更、commitで呼び出される
・actionで同期、非同期なデータの処理、必要に応じてcommitする
これらの基本に基づいて、導入するプロジェクト毎にどうVuexを使っていくかを取捨選択していく形になるという事が分かった。