見出し画像

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の状態をリセットさせます。実装の流れとしては以下のようになり、処理の流れは以下の逆順になっています。

  1. 状態をリセットするためのmutationを定義

  2. それをactionから呼び出す

  3. その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を扱ったプロジェクトから離れることになるので、今後のために忘れないようまとめておきたかったのです。

少しでも参考になれば幸いです。
最後までお読み頂きありがとうございました!

この記事が気に入ったらサポートをしてみませんか?