読み込み中 %

アイコン

Vuex/Reduxでのステートのコピーで気をつけること

taikishiino
2020/04/28

VuexやReduxを使っていると、予期せぬステートの変更が発生してしまうことがあります。 例えば、Vuex/ReduxのObjectステートをローカルに受け渡して、ローカルでステートを更新する時などです。

今回は、Objectのコピーの仕方をいくつか紹介し、それぞれの注意点をご紹介していきます!

Object.assign() と スプレッド構文

Object.assignとスプレッド構文はによるオブジェクトのコピー方法は、シャローコピー(浅いコピー)で参照渡しになります。

【注意点】

オブジェクトをObject.assignを使って値を渡してしまったりすると「参照渡し」として扱われるため、コピー元とコピー先でオブジェクトが共有されてしまい、片方のみ変更しているつもりでも両方に影響が出てしまいます。

以下のようにVuex/ReduxのObjectステートをローカルに受け渡して、ローカルでステートを更新する時などに注意が必要です。

.vue
  data () {
    return {
      item: null,
    }
  }
  computed: {
    ...mapGetters('hoge', [
      'selectedItem'
    ])
  },
  methods: {
    /**
    * @Vuexからローカルステートにコピーする
    */
    setSelectedItem() {
      // Object.assign
      this.item = Object.assign({}, this.selectedItem)
      // スプレッド構文
      this.item = { ...obj }
    },
    updateItem(newVal) {
      // this.selectedItemにも影響を与えてしまう
      this.item = newVal
    },
  }
{
  text: 'hoge',
  date: 2020-05-29T14:34:13.926Z,
  undefined: undefined,
  function: [Function: function]
}

JSON.parse/stringify

JSON.parse(JSON.stringify(obj))は、DeepCopyに近い処理を実現できるハック的なやり方になります。

.vue
  data () {
    return {
      item: null
    }
  }
  computed: {
    ...mapGetters('hoge', [
      'selectedItem'
    ])
  },
  methods: {
    setSelectedItem() {
      // VuexからローカルステートにObjectをDeepCopyする
      this.item = JSON.parse(JSON.stringify(this.selectedItem))
    },
    updateItem(newVal) {
      // this.selectedItemに影響はない
      this.item = newVal
    },
  }

【注意点】

DeepCopyに近いというだけで、完全なDeepCopyではありません。 JSON.parse/stringify は破壊的な変換を伴います。

例えば、オブジェクトのプロパティにDate function undefined が存在する場合、予期せぬ処理をしてしまいます。

  • undefined,functionオブジェクトが消えてしまう
  • dateが文字列型になってしまう
.js
// コピーするオブジェクト
const obj = {
  text: "hoge", 
  date: new Date, // Wed Aug 05 2020 22:23:40 GMT+0900 (JST)
  undefined: undefined,
  function: function(){}
}

// JSON.parse/stringifyでDeepCopyする
const copy = JSON.parse(JSON.stringify(obj));
console.log(copy)
{
  text: "hoge",
  date: "2020-05-29T14:29:02.456Z"
}

LodashのcloneDeep

外部のライブラリを使うことに制約がないのであれば、lodashで提供されているcloneDeepを使うのも良いです。

bash
$ yarn add lodash

lodashのcloneDeepによりオブジェクトをコピーすると、JSON.parse(JSON.stringify(obj))でコピーしていた時の問題が、正しくコピーできる。

  • undefinedのプロパティが欠落しない
  • Functionオブジェクトが欠落しない
  • Dateオブジェクトが復元できる

以下のように、簡単に使用することができます。

.vue
import _cloneDeep from "lodash/cloneDeep";

  data () {
    return {
      item: null
    }
  }
  computed: {
    ...mapGetters('hoge', [
      'selectedItem'
    ])
  },
  methods: {
    setSelectedItem() {
      // VuexからローカルステートにObjectをDeepCopyする
      this.item = _cloneDeep(this.selectedItem)
    },
    updateItem(newVal) {
      // this.selectedItemに影響はない
      this.item = newVal
    },
  }