Hook — 05 : 57 a.m., Bogotá ↔ Tulum debugging huddle
Thunder rolled over the Andes as Mateo, tethered to patchy 4 G on a Mexican beach, shared his screen: sliders updated numbers, yet the chart stayed frozen. “I thought Vue.js handled reactivity for me,” he groaned. A quick inspect showed he’d assigned a new value to a plain object without telling Vue’s proxy system. We replaced the state withreactive
, wrapped the total in acomputed
, and watched the chart animate in real time—just as the café’s generator flickered back to life. That cross-continent epiphany anchors today’s exploration of how Vue’s reactivity really works and how you can bend it to your will.
Why Understanding Reactivity Pays Every Sprint
Reactivity is the invisible gearwork powering data-driven interfaces. Mastering it means fewer “why isn’t this updating?” bugs, leaner components, and smoother performance—vital when your team spans six time zones and countless Wi-Fi speeds. Vue.js 3 exposes three core primitives—ref
, reactive
, and computed
—that let you model state precisely, derive values efficiently, and keep the DOM in sync without over-rendering.
ref
: A Reactive Box for Primitives and Beyond
ref
creates a reactive wrapper around any value. The real data lives inside .value
, and Vue tracks reads and writes automatically.
vueCopyEdit<script setup>
import { ref } from 'vue';
const count = ref(0);
const increment = () => count.value++;
</script>
<template>
<button @click="increment">Clicked {{ count }} times</button>
</template>
Under the Hood
Vue uses ES6 proxies to intercept .value
access; the runtime stores a dependency map so any template expression reading count
re-evaluates when you call count.value++
.
Pro Tip
Wrap DOM references or third-party objects in ref
to keep them reactive without losing their identity.
reactive
: Deep Proxies for Objects and Arrays
For nested structures—shopping carts, form data—use reactive
.
tsCopyEditconst cart = reactive({
items: [] as { id: number; qty: number }[],
});
function add(id: number) {
const found = cart.items.find(i => i.id === id);
found ? found.qty++ : cart.items.push({ id, qty: 1 });
}
Vue converts every property into getters/setters. No .value
needed; property access feels native.
Caveat
Replacing the entire object with assignment breaks the proxy. Clone instead:
tsCopyEditcart.items = [...cart.items]; // triggers change
computed
: Cached Magic for Derived State
computed
wraps a getter function and memoizes its result until any reactive dependency changes.
tsCopyEditconst totalQty = computed(() =>
cart.items.reduce((sum, i) => sum + i.qty, 0)
);
Templates re-render only when cart.items
mutates, not on every click elsewhere.
Lazy Evaluation
The getter runs on first access, then only on invalidation—crucial for expensive calculations or API calls.
Building a Live Currency Converter
vueCopyEdit<script setup lang="ts">
import { ref, computed } from 'vue';
const usd = ref(1);
const rate = ref(55); // Dominican pesos today
const dop = computed({
get: () => usd.value * rate.value,
set: v => (usd.value = v / rate.value),
});
</script>
<template>
<input v-model.number="usd" aria-label="USD" />
<span>=</span>
<input v-model.number="dop" aria-label="DOP" />
</template>
Two-way computed binds both inputs—type in either field, and the other updates without watchers.
Remote-Work Insight Box
During a sprint in São Paulo, our finance app recalculated portfolio value on every keystroke, hammering CPUs. We swapped chained watchers for one computed
total; Vue memoized the heavy math, cutting render time by 70 %. Team-wide battery life thanked us during coffee-shop power outages.
Performance & Accessibility Checkpoints
- DevTools → Component Inspector confirms reactive dependencies; stray values hint at missing
ref
orreactive
. - Lighthouse → Total Blocking Time stays low when derived values move to
computed
instead of watchers. - Screen readers announce changes instantly because Vue patches only affected nodes, preserving focus.
- Memory—destroy composables or components manually if you create refs outside lifecycle hooks.
Common Pitfalls & Quick Rescues
Misstep | Symptom | Fix |
---|---|---|
Directly mutating a reactive array with = | UI won’t refresh | Use push , splice , or assign a clone |
Forgetting .value on a ref in script | TypeScript error / stale data | Always read/write via .value |
Using ref for deep objects | Nested changes ignored | Prefer reactive or ref(reactiveObj) |
Heavy logic in watch | CPU spikes | Move to computed + watchEffect |
Call-Out Table: Pick the Right Tool
API | One-liner purpose |
---|---|
ref | Reactive wrapper for primitives & DOM refs |
reactive | Deep reactive proxy for objects/arrays |
computed | Cached derived value that updates lazily |
watch | Side-effects on state change |
watchEffect | Auto-runs with dependency tracking |
CLI Tidbits
Command | Description |
---|---|
npm create vite@latest my-app -- --template vue | Spin a fresh Vue 3 playground |
npm run dev | Hot-reload with ESBuild |
vue-tsc --noEmit | Type-check refs and computeds without build |
Wrap-Up
ref
, reactive
, and computed
form the backbone of Vue.js’s approachable yet powerful reactivity system. Understand their nuances and you’ll debug faster, ship leaner, and wow teammates—whether they’re on hotel Wi-Fi in Panamá or fiber in Bogotá.
Have a reactivity riddle or victory? Drop it below; I’ll respond between flights and late-night arepa sessions.