vueアドベントカレンダー3日目です!
サンプル
ボタンをクリックすると動的にプロパティを追加するコードがあるとします
<script setup>
import { watch, ref } from 'vue'
const refObj = ref({})
function add() {
refObj.value["add"] = "hello"
}
// addしても動かない
watch(refObj, () => {
console.log("watch ref", refObj.value)
})
</script>
<template>
<div>{{refObj}}</div>
<button @click="add">add</button>
</template>
このwatchはプロパティ追加時には動作しません
deep:trueをつければwatchが動きます
watch(refObj, () => {
console.log("watch ref", refObj.value)
}, {deep: true})
もしくは、reactiveにしてもプロパティ追加をwatchできます
const reactiveObj = reactive({})
function add() {
reactiveObj["add"] = "hello"
}
watch(reactiveObj, () => {
console.log("watch reactiveObj", reactiveObj)
})
なぜ?
watchのソースを読みます
watchの対象がrefのとき
このgetterを監視してcallbackを呼ぶようです
.valueが変わらないのでプロパティ追加してもcallbackは呼ばれません
if (isRef(source)) {
getter = () => source.value
つまりこのように.valueに新しいobjを入れれば動作します
function add() {
refObj.value["add"] = "hello"
// valueが変わるのでwatchが動く
refObj.value = {...refObj.value}
}
deep=trueの場合
traverseで再帰的にプロパティを見ているので追加してもわかります
if (cb && deep) {
const baseGetter = getter
const depth = deep === true ? Infinity : deep
getter = () => traverse(baseGetter(), depth)
}
そしてcallbackを呼ぶ条件に、deepがあるのでcallbackが呼ばれます
if (
deep ||
forceTrigger ||
(isMultiSource
? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i]))
: hasChanged(newValue, oldValue))
) {
reactiveのとき
こちらはreactiveのときの条件です
reactiveGetterの中でtraverseしています
} else if (isReactive(source)) {
getter = () => reactiveGetter(source)
forceTrigger = true
そして先程のcallbackを呼ぶ箇所で、forceTrigger=trueなのでcallbackが呼ばれます
if (
deep ||
forceTrigger ||
(isMultiSource
? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i]))
: hasChanged(newValue, oldValue))
) {
おわり
動作は変わる可能性もあるので注意して下さい
明日は @kosuke-naito さんです!