The Essence of Vue Reactivity: Linking Data and Functions
1. The Essence of Reactivity
Reactivity is about linking data and functions together. When the data changes, the associated functions automatically execute. However, there are constraints on what qualifies as a function and what qualifies as data.
Functions that participate in reactivity:
rendercomputedwatchwatchEffect
Data that participates in reactivity:
- Reactive data (e.g.,
ref,reactive) - Data that is used inside one of the above functions
2. Examples
2.1 A Non-Reactive Example
<template>
<div class="responsive">
<h1>Reactivity Example</h1>
<div>Passed value: {{ count }}</div>
<br>
<div>Doubled: {{ doubled }}</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
count: {
type: Number,
default: 0
}
})
const doubled = ref(props.count * 2)
</script>
Result: The page does not update when the count prop changes. This is because doubled is not reactive. The assignment ref(props.count * 2) happens once and does not involve any function that tracks dependencies. Data-to-data links are not automatically reactive.
2.2 Using watchEffect to Make It Reactive
<template>
<div class="responsive">
<h1>Reactivity Example</h1>
<div>Passed value: {{ count }}</div>
<br>
<div>Doubled: {{ doubled }}</div>
</div>
</template>
<script setup>
import { ref, watchEffect } from 'vue'
const props = defineProps({
count: {
type: Number,
default: 0
}
})
const doubled = ref(0)
watchEffect(() => {
doubled.value = props.count * 2
})
</script>
Result: The page updates when the count prop increases. Why?
watchEffectis a function, andprops.countis reactive data used inside it. So whenprops.countchanges, thewatchEffectcallback runs, updatingdoubled.value.doubledis also reactive data, and it is used in the render function. Whendoubledchanges, the render function re-executes, updating the DOM.
2.3 Passing a Primitive Value to a Function
<template>
<div class="responsive">
<h1>Reactivity Example</h1>
<div>Passed value: {{ count }}</div>
<br>
<div>Doubled: {{ doubled }}</div>
</div>
</template>
<script setup>
import { ref, watchEffect } from 'vue'
const props = defineProps({
count: {
type: Number,
default: 0
}
})
function useDoubled(value) {
const result = ref(0)
watchEffect(() => {
result.value = value * 2
})
return result
}
const doubled = useDoubled(props.count)
</script>
Result: The page does not update when the count prop changes. Why? The useDoubled function receives props.count as a primitive number. Inside watchEffect, it uses value which is just a number—not a reactive reference. Therefore, no dependency is tracked, and the effect never reruns.
2.4 Using computed
<template>
<div class="responsive">
<h1>Reactivity Example</h1>
<div>Passed value: {{ count }}</div>
<br>
<div>Doubled: {{ doubled }}</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
count: {
type: Number,
default: 0
}
})
const doubled = computed(() => {
return props.count * 2
})
</script>
Result: The page updates when the count prop changes. Why?
computedis a function that readsprops.count(reactive). Socomputedtracksprops.countand recalculates whenever it changes.doubled(the computed ref) is reactive and used in the render function, so changing it triggers a re-render.
2.5 Passing the Entire Props Object
<template>
<div class="responsive">
<h1>Reactivity Example</h1>
<div>Passed value: {{ count }}</div>
<br>
<div>Doubled: {{ doubled }}</div>
</div>
</template>
<script setup>
import { ref, watchEffect } from 'vue'
const props = defineProps({
count: {
type: Number,
default: 0
}
})
function useDoubled(propsObj) {
const result = ref(0)
watchEffect(() => {
result.value = propsObj.count * 2
})
return result
}
const doubled = useDoubled(props)
</script>
Result: The page updates when the count prop changes. Why?
propsis a reactive object. When we passpropstouseDoubled, thewatchEffectinside it readspropsObj.count. SincepropsObjis reactive, the effect tracks thecountproperty.- When
countchanges, the effect runs, updatingdoubled. doubledis reactive and used in the render function, so the view updates.
Note: This pattern (passing the whole props object) is common in libraries like VueUse.
Summary
Reactivity in Vue depends on establishing a connection between reactive data and functions (render, computed, watch, watchEffect). A direct assignment between two reactive values without an intervening reactive function will not be tracked. To create a reactive derivation, always use computed or a watcher inside a setup scope that reads the reactive source.