Consider a simple component Comp.vue
file that only logs a message in the console when updated:
// Comp.vue
<script setup>
import { onUpdated } from 'vue'
onUpdated(() => {
console.log('updated component')
})
</script>
<template></template>
An App.vue
file mounts Comp.vue
and also logs a message in its onUpdated
hook:
// App.vue
<script setup>
import Comp from './Comp.vue'
import { ref, onUpdated } from 'vue'
onUpdated(() => {
console.log('updated parent')
})
const text = ref('')
const data = ref()
</script>
<template>
<input v-model="text"/>
<Comp/>
</template>
There's a text
ref bound to an input element to trigger onUpdated
on the parent. At this point, writing on the text input only logs 'updated parent'
in the console.
If an object with a reactive property is passed to Comp
as a prop, writing on the input text logs both 'updated component'
and 'updated parent'
in the console:
<Comp :data={ data }/>
That is, Comp
's onUpdated
hook is being triggered with changes to App
's state. Here's a demo.
To avoid this, a computed property could be passed instead:
const computedData = computed(() => ({ data: data.value }))
// ...
<Comp :data="computedData"/>
However, I'd like to know why passing { data }
directly causes Comp
onUpdated
hook to be triggered when text
is updated.
When you use a ref in a template, and change the ref's value later, Vue automatically detects the change and updates the DOM accordingly... When a component is rendered for the first time, Vue tracks every ref that was used during the render. Later on, when a ref is mutated, it will trigger a re-render for components that are tracking it. [1]
I take this to mean that when the text
ref is updated, the DOM of the component tracking it, i.e. App
, is re-rendered. This re-computes, if necessary, the data
prop passed to Comp
. Then, in the situation described in the question, as Estus Flask pointed out, the recomputed prop results in a new object, different to the previous one passed, causing the Comp
state to be updated.
However, if the plain object does not reference variables declared in the script section, the data
prop is not recomputed.
<!-- Does not update when input changes -->
<Comp :data="{ data: 2 }"/>
The prop is only recomputed if the passed object references variables declared on the script section. As mentioned in the question, if the object includes reactive data, a computed property may instead be used. If there's no reactive data, the object should be defined in the script section and then passed as a prop.
<script setup>
const reactiveData = ref(0)
const regularData = 0
const computedProp = computed(() => ({ data: reactiveData.value }))
const staticProp = { data: regularData }
</script>
<tempalte>
<!-- Do not update when input changes -->
<Comp :data="computedProp"/>
<Comp :data="staticProp"/>
</template>
See full example.
[1] Why Refs?, Vue.js