Skip to content

Understanding Reactivity

Reactivity is the mechanism that makes Vue update the DOM when data changes. It’s not magic — it’s a precise system with defined rules. Understanding those rules helps you avoid the most common Vue bugs.

<script setup lang="ts">
let count = 0 // plain variable
function increment() {
count++ // changes the variable, but Vue doesn't know
}
</script>
<template>
<p>{{ count }}</p>
<button @click="increment">+1</button>
</template>

Click the button — count increases in memory, but the paragraph never updates. Vue has no way to know the variable changed.

Vue uses JavaScript Proxies to wrap reactive state. When you read a reactive property, Vue registers a dependency: “this template depends on this value.” When you write to it, Vue knows to re-run any template or computed that depended on it.

Read reactive value → Vue notes the dependency
Write reactive value → Vue re-renders dependents

This happens automatically. You don’t call setState, dispatch an action, or manually trigger updates. You just mutate the reactive value and Vue handles the rest.

Only values created with Vue’s reactivity APIs are reactive:

  • ref(value) — wraps any value in a reactive reference
  • reactive(object) — makes an entire object deeply reactive
  • computed(() => ...) — a derived reactive value

Plain let variables, const objects, and function arguments are not reactive. Changing them won’t trigger re-renders.

<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0) // ✅ reactive
function increment() {
count.value++ // Vue detects this and re-renders
}
</script>
<template>
<p>{{ count }}</p>
<button @click="increment">+1</button>
</template>

Now the paragraph updates when the button is clicked. count is a reactive ref — Vue tracks reads of count.value in the template and re-renders when it changes.

Vue only re-renders components that actually depend on changed data. If PersonCard renders name and birthYear, only PersonCard re-renders when name changes — not the entire tree.

This is why Vue apps feel fast even with large component trees. Reactivity is surgical, not broad.

  1. Create a new component ReactivityDemo.vue.
  2. Add a plain let count = 0 and a button that increments it. Confirm it doesn’t update in the browser.
  3. Change it to const count = ref(0). Confirm the browser now updates.
  4. Add a second variable const message = ref('hello') and a button that changes it to 'world'. Confirm both values update independently.
  • Vue tracks reactive state with Proxies — reads register dependencies, writes trigger re-renders.
  • Plain let/const variables are not reactive — changes don’t update the DOM.
  • ref(), reactive(), and computed() create reactive state.
  • Vue only re-renders components that depend on the changed value — not the entire tree.