Vue 3 and Nuxt · Vue 3 Fundamentals
Reactivity: ref, reactive, computed and watch
Understand Vue's reactivity system: ref and reactive state, cached computed values, batched watchers, and why destructuring loses reactivity.
What you'll build
A shopping-cart model built from Vue's reactivity primitives, run directly in Node: a computed total that's cached until its dependencies change, a watcher that runs once for several changes, and the destructuring mistake that silently breaks reactivity, with the toRefs fix.
The idea
ref(value)holds any value in.value;reactive(object)makes an object deeply reactive through a proxy.computedderives values; it's cached and recomputed only when a dependency changes and the value is read.watchruns side effects when sources change. Callbacks are batched: several synchronous changes trigger one run (afternextTick).- Destructuring a reactive object copies plain values; use
toRefs(or keep usingstate.x) to stay connected.
The code
import { reactive, ref, computed, watch, nextTick, toRefs } from "vue";
const cart = reactive({ items: [{ name: "Mug", price: 8.5, qty: 2 }] });
const discount = ref(0);
const total = computed(() => {
console.log(" (computing total)");
const sum = cart.items.reduce((acc, item) => acc + item.price * item.qty, 0);
return Number((sum * (1 - discount.value)).toFixed(2));
});
console.log("total:", total.value);
console.log("read again:", total.value); // cached: no recomputation
watch(total, (now, before) => console.log(`watch: total ${before} -> ${now}`));
cart.items.push({ name: "Teapot", price: 24, qty: 1 });
discount.value = 0.1;
console.log("changed twice; watcher hasn't run yet");
await nextTick(); // the watcher runs once for both changes
const state = reactive({ count: 0 });
let { count } = state; // a plain copy of the number
const { count: countRef } = toRefs(state); // a ref linked to state.count
state.count = 5;
console.log(`destructured: ${count}, toRefs: ${countRef.value}`);
Output
(computing total) total: 17 read again: 17 changed twice; watcher hasn't run yet (computing total) watch: total 17 -> 36.9 destructured: 0, toRefs: 5
Run this on your computer — see “Get Started” for setup.
Common mistakes
- Destructuring
reactiveobjects or props and losing reactivity; usetoRefs,computed, or access through the object. - Replacing a whole
reactiveobject (state = reactive({...})) instead of mutating it; use arefwhen you need to replace the value. - Side effects inside
computed; computed getters should be pure, usewatchfor effects. - Expecting watchers to run synchronously after each change; they're batched until the next tick.
Key points
- ref for any value, reactive for objects; computed is cached.
- Watchers are batched and run once per tick.
- Destructuring breaks reactivity unless you use toRefs.
Further reading: Reactivity fundamentals · Computed properties · Watchers
Practice questions
What's the difference between ref and reactive?
Answer
ref wraps any value (accessed via .value, replaceable); reactive returns a deep proxy of an object, which can't be replaced wholesale and loses reactivity when destructured.
When does a computed value recompute?
Answer
Only when one of its reactive dependencies has changed and the value is read again; otherwise the cached value is returned.
Why did the watcher run once after two changes?
Answer
Watcher callbacks are batched and flushed asynchronously (before the next render by default), so several synchronous changes trigger one run.
Open in the app to mark lessons complete, take notes and track your progress.