Vuexで管理しているデータを変更しても検知されず、DOMに反映されなくて困ったのでメモ。
やりたいこと
- カードが開いているか閉じているかのフラグ(isOpen)をstoreで管理
- isOpenがtrueになったら、実際にカードを開く
状況
カードがOpenされているかのフラグをstoreで管理
store.js
actionsPayload.openCard = {
groupKey: {
type: Number,
required: true
}
};
actions.openCard = (store, payload) => {
store.commit('openCard', payload);
};
mutationsPayload.openCard = {
groupKey: {
type: Number,
required: true
}
};
mutations.openCard = (state, payload) => {
// 一度全部閉める
if (state.cards.items === null || state.cards.items === undefined) return;
state.cards.items = state.cards.items.map((card) => {
card['isOpen'] = false;
return card;
});
// 一個開ける
Vue.set(state.cards.items[payload.groupKey], 'isOpen', true);
};
カード見出しをクリックしたら詳細を表示する
:v-if="card.isOpen"
がtrueになったらカード詳細表示されてほしい・・
card.vue
<template>
<div>
<div :v-if="!card.isOpen" @click="handleClick">カード見出し</div>
<div :v-if="card.isOpen">カード詳細</div>
</div>
</template>
<script>
export default {
props: {
card: {
type: Object,
required: true
},
groupKey: {
type: Number,
required: true
}
},
methods: {
async handleClick() {
await this.$store.dispatch('store/openCard', { groupKey: this.groupKey });
}
}
};
</script>
困ったこと
上記のコードだとカード詳細はオープンしません。
vue devtoolsで確認して、isOpen: true
になってるのになぜ😢
解決法
Objectからドット表記でisOpenにアクセスせず、直接propsに渡してあげる
card.vue
<template>
<div>
<!-- card.isOpenをpropsのisOpenに変更 -->
<div :v-if="!isOpen" @click="handleClick">カード見出し</div>
<div :v-if="isOpen">カード詳細</div>
</div>
</template>
<script>
export default {
// 略
props: {
// 追加
isOpen: {
type: Boolean,
default: false
}
}
};
</script>
vueはObjectの中身の一部が変更しても検知してくれないみたいです。
(実際に詰まったのは、多次元配列だったので、このサンプルのように1次元配列だと変更を検知してくれる場合もあるかも?)
実際に公式でもこう書いてあります。
Vue.js はプロパティの追加または削除を検出できません。
リアクティブの探求 — Vue.js
どうしても変更が検知されない場合の奥の手
vm.$forceUpdate()
API — Vue.js
forceUpdate()すれば強制的に初期化され、追加したデータがリアクティブにDOMに反映されます。
ただ、初期化することで全てのデータが取り直しとなり、あまり良い処理ではありません・・。
もし Vue で強制更新をする必要な場面に遭遇する場合、99.99% のケースであなたは何かを間違えています。
しかし、もし上記の可能性を排除し、手動で強制更新をする非常に稀な状況と認識しているならば、$forceUpdate を用いることで強制更新をすることができます。
特別な問題に対処する — Vue.js