owned mediaウェブ制作に役立つコンテンツを発信中!

v-modelを使ってオブジェクトや配列のstateの値を更新するときに発生する「do not mutate vuex store state outside mutation handlers」エラーの対応

最終更新日: Update!!
Vue.jsで、フォーム要素に入力した値を変数に格納して同期する場合などには、v-modelがとても便利ですが、stateの値へ反映させる際にはケースによって少し注意が必要になります。というのも、stateの値を更新する際には、mutationsでの処理が必要になるためです。そうしないと「do not mutate vuex store state outside mutation handlers」というエラーが発生してしまいます。全てのケースではありませんが、更新する値が配列やオブジェクト型の場合にv-modelを使ってstateの値を更新した際にこのエラーが発生した時の対応を備忘録として残しておきたいと思います。   一般的にはcomputedの中で、getメソッドとsetメソッドを使って値の取得と更新の両方に対応させる形になります。(参考記事「v-modelでstateやgettersを変更したい時に使える算出プロパティのset()関数」)まずはコンポーネント、もしくはページテンプレート側の処理をみていきます。 【index.vue】
<template>
  <div>
    <span>名前:<input v-model="fromData.name" type="text"></span>
    <span>年齢:<input v-model="fromData.age" type="text"></span>
    <span>住所:<input v-model="fromData.address" type="text"></span>
  </div>
</template>

<script>
export default {
  .....
  computed: {
    fromData: {
      get() {
        return this.$store.getters.getMembers 
      },
      set(value) {
        this.$store.dispatch('fetchMembers', value)
      } 
    }
  }
  .....
}
<script>
  input要素にv-modelを設定し、computedで定義した処理を紐づけるようにします。computed側では、getメソッドではstateの値をgetter経由で取得し、値が入力した時にはsetメソッドで、actionsに定義した処理を実行し、mutations経由で入力した値がstateに更新されるという流れです。そのstoreの処理はこのような形です。 【store/index.js】
export const state = () => ({
  members: {
    name: '',
    age: '',
    address: ''
  }
})

export const mutations = {
  setMembers(state, payload) {
    state.members = payload
  }
}

export const actions = {
  fetchMembers({ commit }, payload) {
    return commit('setMembers', payload)
  }
}

export const getters = {
  getMembers: (state) => state.members
}
  今回ポイントとなるのは今回対象となる値はオブジェクト型であるという点です。この形でフォーム要素に値を入力するとこのようなエラーが発生してしまいました。
[vuex] do not mutate vuex store state outside mutation handlers
  どうやら、stateの値を更新できるのはmutationのみですよと警告されているようです。もちろん、それは理解しているのと、コンポーネント側からはdispatchメソッドでactionsの処理を呼び出しているはずなのですが、、試しにオブジェクト型ではなく、文字列型の値を同じように更新する場合はエラーは発生しませんでした。   そこで対処法になりますが、算出プロパティのgetメソッドで返している値を、stateからgetters経由で取得しているものを直接使うのではなく「Object.assign」でオブジェクト型のデータとしてコピーしてから使うようにしました。こうすることでエラーは解消されました。
fromData: {
  get() {
    return Object.assign({}, this.$store.getters.getMembers)
  },
  set(value) {
    this.$store.dispatch('fetchMembers', value)
  } 
}
  どうやらオブジェクト型の値は変数に代入される際に参照渡しとなるため影響しているようです。  
値渡しと参照渡し
JavaScriptでは値が変数に格納される際にデータの型によって少し異なるという特性があります。それが「値渡し」と「参照渡し」です。  
値渡し 格納される値そのものが格納先の変数に渡される (文字列型、数値型、Boolean型、Null、Undefinedなど)
参照渡し 格納される値を参照するデータとして格納先の変数に渡される (配列型、オブジェクト型、Date型など)
  配列やオブジェクト型の値はあくまで値を参照するデータとして扱われるというイメージになりますね。  
Object.assign()やArray.from()でオブジェクト型・配列型のデータを複製する
このような場面で、使えるのが「Object.assign()」や「Array.from()」といったオブジェクト型や配列型を複製できるメソッドです。どちらも非破壊的な処理になるので、元のオブジェクトや配列の値を変えることなく複製できます。  
Object.assign( target, source ) 第一引数にはコピー先のオブジェクトを、第二引数にコピー対象となるオブジェクトを指定
Array.from( array ) 引数にコピーする対象となる配列データを指定
 
  少し本題を逸れてしまいましたが、JavaScriptの仕様として覚えておきたいポイントになりますね。Vue.jsではリアルタイムで入力データをstateの値として同期するケースも多いかと思いますので、気をつけていきたいですね。
  • はてなブックマーク
  • Pocket
  • Linkedin
  • Feedly

この記事を書いた人

Twitter

sponserd

    keyword search

    recent posts

    • Twitter
    • Github
    contact usscroll to top
      • Facebook
      • Twitter
      • Github
      • Instagram