現状
vuexでmutationやactionを送るときは、定数を使うとこんな感じになると思います。
<template lang="pug">
#app
.counter
button(@click="onDecrementClick") -
span {{ $store.state.count }}
button(@click="onIncrementClick") +
div
input(type="number", v-model="value")
button(@click="onAddButtonClick") add
</template>
<script>
import * as mutationTypes from '@/store/mutationTypes';
export default {
name: "App",
data() {
return {
value: 0
}
},
methods: {
onIncrementClick() {
this.$store.commit(mutationTypes.INCREMENT);
},
onDecrementClick() {
this.$store.commit(mutationTypes.DECREMENT);
},
onAddButtonClick() {
const value = parseInt(this.$data.value, 10);
this.$store.commit(mutationTypes.ADD, { value });
}
}
};
</script>
<style lang="scss" scoped>
.counter {
span {
padding: 0 10px;
}
}
</style>
store側の設定はこんな感じになります。
import Vue from 'vue';
import Vuex from 'vuex';
import * as mutationTypes from './mutationTypes';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
// インクリメントする
[mutationTypes.INCREMENT](state) {
state.count += 1;
},
// デクリメントする
[mutationTypes.DECREMENT](state) {
state.count -= 1;
},
// 加算する
[mutationTypes.ADD](state, { value }) {
state.count += value;
}
}
});
mutation名のtypoを防ぐため定数にしており、そんなに悪くありませんが、一つだけ困ることがあります。
それは引数は何を渡すかを確認するのが面倒ということで、mutationTypes.ADD
からコードジャンプしても定数の宣言部分に飛ぶだけで、肝心な引数の情報は載っていないので毎回store.jsで定義されているところを探しに行く羽目になります。
JS DOCも非常に書きづらくて、payloadの部分だけ書くというのはどうなんだろうって気になって、結局上のようにシングルラインコメントで操作することだけ書いています。
reduxの場合は、そもそもactionオブジェクトを作る関数があって、それを介して送っていました。
this.props.dispatch(createAction(10));
vuexは直接タイプ名をセットできるようにしてstoreへの送信を楽にさせたのだと思いますが、規模が大きくなると今回のようなやりづらさが出るんじゃないかなと思いました。
そこで冗長になってしまいますが、mutationオブジェクトを作る関数を用意することで、引数問題を解消しようというのが今回の記事です。
mutationオブジェクトの作成
基本的にvuexはタイプ名、payloadという順番で送ると思いますが、タイプ名を含めたオブジェクトで送ることができます。
// 通常の送り方
this.$store.commit('typeName', { value });
// オブジェクトでの送り方
this.$store.commit({
type: 'typeName',
value
});
このオブジェクト部分を関数で作るとこんな感じになります。
/**
* mutationオブジェクトを作る
* @param {number} value - mutationに送信する値
*/
function createMutation(value) {
return {
type: 'typeName',
value
};
}
// mutationオブジェクトを作って、送信する
this.$store.commit(createMutation(10));
関数になったことでJS DOCも書きやすくなり、何を渡してあげなければいけないかが分かりやすくなったと思います。その代わり冗長になってしまいましたが・・・。
actionオブジェクトの作成
actionも同様にオブジェクトを作る関数を作ればいいのですが、その度にactionType名を作るのも面倒かなと思いました。loggerにもデフォルトでは表示されず、actionTypeとマッチするものをフックするだけの役割しかないなら、今回のように関数を別に用意した場合は不要かなと思いました。そこでredux-thunkのように非同期処理をするアクションだけを用意して、それを使いまわすようにしました。
import Vue from 'vue';
import Vuex from 'vuex';
import * as mutationTypes from './mutationTypes';
import * as actionTypes from './actionTypes';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
data: 0
},
mutations: {
// データをセットする
[mutationTypes.SET_DATA](state, { data }) {
state.data = data;
}
},
actions: {
// 関数を受け取り、commitを渡して実行するだけのアクション
[actionTypes.THUNK]({ commit }, { method }) {
return method(commit);
}
}
});
import { THUNK } from './actionTypes';
import { setData } from './mutationCreators';
/**
* thunkアクションを生成する
* @param {function} method - commitを引数にした関数
*/
function createThunkAction(method) {
return {
type: THUNK,
method
};
}
/**
* データを取得する(テストなので通信はしない)
*/
export function fetchData() {
return createThunkAction((commit) => {
return new Promise((resolve) => {
commit(setData(0));
window.setTimeout(() => {
commit(setData(Math.random()));
resolve();
}, 500);
});
});
}
サンプルコード
こちらにサンプルコードを置きましたので興味があるかたは見てください。