mutationsとactions、その運用方法について [Vue.js, Vuex]
今回の記事では、Vuexのactionとmutationに関してまとめたいと思います。(Vuex自体の説明は含みません。)
またこの記事は実際の実装よりも自身の調べた内容をまとめた、備忘録のような色が強いです。
なのでHow to の部分・実際のコードの部分はあまり参考にならないかもしれません。あらかじめご了承ください。
環境としては、Rails6.x と Vue 3.x の構成にTypeScriptを加えて運用していました。
mutationsとactions
mutatoinsとは
stateの値を変するメソッド
storeで定義されるstateの値達は、実はグローバル変数として定義されている。なのでどこからでも変更が可能であるゆえ、変更する場所を制限しなければ、stateの値の変更について追跡しづらい。
なのでstateの値の変更を追跡しやすいよう、変更できる箇所を制限する、その役割を担うのがmutationsです。
mutationを呼び出す為には、コンポーネント側 (後述するactions側) から、commit することでmutationを利用することができる。
// ~.ts: store側。mutationsを定義する。
export default new Vuex.Store({
state: {
count: 2
},
mutations: {
// mutationsの第一引数には、stateを取る。
increment(state, number) {
state.count += number;
}
}
});
// ~.vue: コンポーネント側。mutationsをcommitする。
<script lang="ts">
export default {
setup(props) {
const increment = (): void => {
this.$store.commit("increment", 2);
}
return {
increment
}
}
}
</script>
mutationの特徴: 同期的な処理しかできない
mutationで非同期的な処理ができる場合を仮定します。仮にstateの値を変更する2つのmutationsがあった場合、コンポーネント側からはそれらが同期的か、非同期的かを理解することはできません。
なのでmutationsで非同期的な処理を可能にすることは、プログラムを大変ややこしくするため、できないようになっています。(恐らく
// コンポーネント側 (もしmutationが非同期的な処理を行えた場合
<template>
<div>
<button @click="increment(2)">+2</button> // もしかしたら10秒後に+2されるかもしれない
<button @click="decrement(2)">-2</button> // もしかしたら5秒後に-2されるかもしれない
</div>
</template>
<script lang="ts">
export default {
setup(props) {
// mutationを定義
const increment = (): void => {
this.$store.commit("increment", 2);
}
const decrement = (): void => {
this.$store.commit("decrement", 2);
}
return {
increment,
decrement
}
}
}
</script>
なので非同期的な処理を可能にする為に、Vuexは actions というプロパティを用意しています。
actions とは
mutationsとは異なり、非同期の処理も記載することができるプロパティ。
第1引数にはcontextを取り、actionsの中からstateの取得やmutationのcommitなどもできます。
actionsはコンポーネント側から、dispatch を用いて呼び出すことができます。
// store
export default new Vuex.Store({
state: {
count: 2
},
mutations: {
increment(state, number) {
state.count += number;
}
},
// actionsを追記
actions: {
increment(context, number) {
context.commit('increment', number);
// context には色々なものが含まれている (e.g. context.state, context.dispatch('') など
}
}
});
// コンポーネント側actionをdispatch
<script lang="ts">
export default {
setup(props) {
const increment = (): void => {
this.$store.dispatch("increment", 2);
}
return {
increment
}
}
}
</script>
mutationの運用方法
actionからしかmutationは呼ばない
actionとmutationは少し似た性質を持っています。それぞれ運用方法を決めておかないと、のちのち管理が面倒になります。なのでここではmutaionとactionの運用方法の1つを紹介します。
運用方法: コンポーネントからはcommitしない ( = コンポーネントからmutationは使わない。
stateの値を変更できるmutationを利用する場合は、コンポーネントからactionをdispatchし、そのactionの中からmutationをcommitする。 (サンプルコードで紹介
つまりmutationはactionの中からしかcommitしないようにすることで、railsで言うプライベートメソッドのように扱うようにする。それにより処理の流れを限定し、追跡しやすくする、といった構成。
何でもできるVueだからこそ、縛りを与えることで理解しやすくする。 (これが設計かな?
mutaiont-types.js
またmutaionの運用方法の一つとして、全てのmutationをmutation-types.js にまとめる、という管理方法があります。
これにより、開発者は一目でアプリに定義されているmutationの一覧を確認することができる。また、同一のmutation-typeを定義することを防ぐことができる(恐らく)。
export const USER_ADD = 'USER_ADD'
export const USER_SET = 'USER_SET'
export const USER_UNSET = 'USER_UNSET'
export const USER_SET_LIST = 'USER_SET_LIST'
export const USER_UPDATE = 'USER_UPDATE'
:
参考: https://vuex.vuejs.org/guide/mutations.html#using-constants-for-mutation-types
サンプルコード
モーダルを閉じた際にstateの値をリセットする
上記で紹介した、コンポーネントからはactionのみを呼び出し、その中からのみmutationを呼ぶ、といった運用方法のサンプルコードを一つ紹介します。
モーダルを閉じた際に、state.userの状態をリセットさせます。実装の流れとしては以下のようになり、処理の流れは以下の逆順になっています。
状態をリセットするためのmutationを定義
それをactionから呼び出す
そのactionをコンポーネントからdispatchする
<template>
<article>
:
<transition name="modal" appear>
<div class="modal__wrap" v-if="state.showNewModal">
<div class="modal__inner modal__registration">
<article-form
v-model:name="state.user.name"
v-model:tel="state.user.tel"
v-model:zipCode="state.user.zipCode"
@save-user="createUser"
@close="closeNewModal"
>
</article-form>
</div>
</div>
</transition>
</article>
</template>
<script lang="ts">
import { defineComponent, reactive, computed } from "vue";
import UserForm from '~/components/users/userForm.vue'
import { useStore } from 'vuex'
export default defineComponent({
components: {
UserForm
},
setup(props) {
const store = useStore()
const state = reactive({
user: {
id: '',
name: '',
tel: '',
zipCode: '',
},
showNewModal: false,
})
const openNewModal = (): void => {
state.showNewModal = true
}
const closeNewModal = (): void => {
unsetUserAttributes()
state.showNewModal = false
}
:
const unsetUserAttributes = (): void => {
store.dispatch('user/unsetUserAttributes', state.user)
}
:
return {
state,
openNewModal,
closeNewModal
};
}
});
</script>
import { USER_SET, USER_UNSET } from '~/store/mutation-types'
:
// actionを定義
const actions = {
:
},
unsetUserAttributes({ commit }, params) {
commit(USER_UNSET, params)
},
}
// mutationを定義
const mutations = {
[USER_SET](state, user) {
state.user = user
},
[USER_UNSET](state, user) {
user.name = null
user.tel = null
useer.zipCode = null
}
}
:
export const USER_UNSET = 'USER_UNSET'
おわり
今後少しVueを扱ったプロジェクトから離れることになるので、今後のために忘れないようまとめておきたかったのです。
少しでも参考になれば幸いです。
最後までお読み頂きありがとうございました!
この記事が気に入ったらサポートをしてみませんか?