【Vue3】CompositionAPIでpropsの変更を検知する
Vue3 Composition API でscript
内でprops
の変更にを検知して特定のメソッドを呼び出す方法の解説です。
前提
親コンポーネントからVuexに保存されているstate
を取得し、その値をprops
として子コンポーネントに渡している場合に、子コンポーネントから特定のprops
の変更に応じてメソッドを実行したい場合の対処法です。
具体的なユースケースとしては、現在地の位置情報が変化した際にVuexのstate
に保存し、state
の現在地をprops
で受け取りマップの中心を動かすメソッドを呼び出す場合などが挙げられます。
今回の例では分かりやすいように、メッセージを表示する例で紹介します。
import { createStore } from "vuex";
export default createStore({
state: {
msg: "default msg",
},
mutations: {
setMsg(state, msg) {
state.msg = msg;
},
},
});
<template>
<Child :msg="msg" />
</template>
<script lang="ts">
import { defineComponent, computed } from "vue";
import { useStore } from "vuex";
import Child from "@/components/Child.vue";
export default defineComponent({
name: "Home",
components: {
Child,
},
setup() {
const store = useStore();
const msg = computed(() => store.state.msg);
// 0.5秒後に書き換え
setTimeout(() => {
store.commit("setMsg", "changeMsg");
}, 500);
// 1秒後に書き換え
setTimeout(() => {
store.commit("setMsg", "changeMsg2");
}, 1000);
return {
msg,
};
},
});
</script>
<template>
<div class="child">
<h1>{{ msg }}</h1>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "Child",
props: {
msg: String,
},
setup(props) {
// ここでprops.msgが変更されたらメソッドを呼び出す
},
});
</script>
結論
index.ts
とParent.vue
は変更不要です。
index.ts
import { createStore } from "vuex";
export default createStore({
state: {
msg: "default msg",
},
mutations: {
setMsg(state, msg) {
state.msg = msg;
},
},
});
Parent.vue
<template>
<Child :msg="msg" />
</template>
<script lang="ts">
import { defineComponent, computed } from "vue";
import { useStore } from "vuex";
import Child from "@/components/Child.vue";
export default defineComponent({
name: "Home",
components: {
Child,
},
setup() {
const store = useStore();
const msg = computed(() => store.state.msg);
// 0.5秒後に書き換え
setTimeout(() => {
store.commit("setMsg", "changeMsg");
}, 500);
// 1秒後に書き換え
setTimeout(() => {
store.commit("setMsg", "changeMsg2");
}, 1000);
return {
msg,
};
},
});
</script>
Child.vue
を次のように変更すれば変更を検知できます。
<template>
<div class="child">
<h1>{{ msg }}</h1>
</div>
</template>
<script lang="ts">
- import { defineComponent } from "vue";
+ import { defineComponent, toRefs, watch } from "vue";
export default defineComponent({
name: "Child",
props: {
msg: String,
},
setup(props) {
+ const { msg } = toRefs(props);
+ watch(msg, () => {
+ // ここで任意のメソッドを呼び出す
+ });
},
});
</script>
解説
NGパターン
真っ先に思いつきそうな方法として、watch
の引数にprops.msg
と指定する方法はうまくいきません。
watch
の引数はリアクティブなオブジェクトである必要があるため、props.msg
(今回はstring
)は指定できません。
watch(props.msg, () => {
// ここで任意のメソッドを呼び出す
});
ですが、リアクティブなオブジェクトであるprops
をwatch
に指定すると、propsのいずれかの値が変化した場合に毎回呼び出されてしまいます。
watch(props, () => {
// ここで任意のメソッドを呼び出す
});
toRefsで値をリアクティブで取り出す
解決策としては、特定の値をリアクティブで取り出すために、toRefs
メソッドを使います。
toRefs
メソッドはオブジェクトの特定のプロパティの参照を作成する関数です。
参考:https://v3.ja.vuejs.org/api/refs-api.html#toref
toRefs
を使って特定のプロパティの参照オブジェクトを作成することで、watch
の引数に指定できるようになります。
<template>
<div class="child">
<h1>{{ msg }}</h1>
</div>
</template>
<script lang="ts">
- import { defineComponent } from "vue";
+ import { defineComponent, toRefs, watch } from "vue";
export default defineComponent({
name: "Child",
props: {
msg: String,
},
setup(props) {
+ const { msg } = toRefs(props);
+ watch(msg, () => {
+ // ここで任意のメソッドを呼び出す
+ });
},
});
</script>
Vue3 Composition API でscript
内でprops
の変更にを検知して特定のメソッドを呼び出す方法でした。
Discussion