VuexやReduxを使っていると、予期せぬステートの変更が発生してしまうことがあります。 例えば、Vuex/ReduxのObjectステートをローカルに受け渡して、ローカルでステートを更新する時などです。
今回は、Objectのコピーの仕方をいくつか紹介し、それぞれの注意点をご紹介していきます!
Object.assign() と スプレッド構文
Object.assign
とスプレッド構文はによるオブジェクトのコピー方法は、シャローコピー(浅いコピー)で参照渡しになります。
【注意点】
オブジェクトをObject.assign
を使って値を渡してしまったりすると「参照渡し」として扱われるため、コピー元とコピー先でオブジェクトが共有されてしまい、片方のみ変更しているつもりでも両方に影響が出てしまいます。
以下のようにVuex/ReduxのObjectステートをローカルに受け渡して、ローカルでステートを更新する時などに注意が必要です。
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に近い処理を実現できるハック的なやり方になります。
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が文字列型になってしまう
// コピーするオブジェクト
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を使うのも良いです。
$ yarn add lodash
lodashのcloneDeepによりオブジェクトをコピーすると、JSON.parse(JSON.stringify(obj))
でコピーしていた時の問題が、正しくコピーできる。
- undefinedのプロパティが欠落しない
- Functionオブジェクトが欠落しない
- Dateオブジェクトが復元できる
以下のように、簡単に使用することができます。
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
},
}