AcademyVue
Watchers: watch() and watchEffect()
watch() reacts to sources you name explicitly; watchEffect() auto-tracks whatever it reads — the same trade computed vs method makes, one level up.
2 min readUpdated 3 exercises
watch() needs a ref, a reactive object, or a getter — never a plain value
watch's first argument has to be something Vue can subscribe to. A ref, a reactive() object, an array of either, or a getter function are the only valid shapes:
import { ref, reactive, watch } from 'vue'
const query = ref('')
watch(query, (newVal, oldVal) => { /* runs on every change to query */ })
const settings = reactive({ theme: 'light' })
watch(() => settings.theme, (newTheme) => { /* a getter, not settings.theme directly */ })watch(query.value, cb) and watch(settings.theme, cb) are the two shapes of the same mistake — both pass the current value, read once, at the moment watch is called. There is nothing reactive about a plain string; the callback registers and then never fires again, silently.
watchEffect() auto-tracks whatever it reads, and runs immediately
import { watchEffect } from 'vue'
watchEffect(() => {
console.log(`price: ${price.value}, quantity: ${quantity.value}`)
})No source argument at all: watchEffect's callback runs once immediately, and Vue records every ref or reactive property it read during that run as a dependency. Change any of them and it reruns automatically — including a dependency added conditionally on a later run, since tracking happens fresh on every execution.
The bug watch() invites that watchEffect() cannot
// Reads both price and quantity, but only watches price — bumping
// quantity alone never triggers a recalculation.
watch(price, () => {
total.value = price.value * quantity.value
})This compiles, runs, and looks correct until the exact moment someone changes quantity without also changing price in the same interaction. watchEffect(() => { total.value = price.value * quantity.value }) cannot have this bug — it discovers both dependencies by reading them, so there is no source list to under-specify.
When watch() is still the right call
watchEffect is not a strict upgrade. watch gives you the previous value alongside the new one, lets you delay the first run instead of firing immediately (the default — pass { immediate: true } to opt into watchEffect's behavior), and lets you watch something without reading it inside the callback at all — useful when the callback's job is a side effect the source value merely triggers, not something the callback needs the value of directly. Reach for watch when you need the old value, explicit control over the first run, or a source list narrower than everything the effect happens to touch; reach for watchEffect everywhere else.
What interviewers check
Whether you can point at a watch call and immediately name every value its callback reads that is not in its source list — that list is exactly the set of changes the watcher will silently ignore.
Now practice it
1 quiz · 2 drillsReading this page is the cheap half. These are the exercises that make you use it.
- WatchersTwo watch() calls each given a plain, already-unwrapped value instead of a ref or getter — neither ever fires again.DrillSolve · EasyDrillSolve · Easy
- watchEffectProA watch(price, ...) that silently ignores quantity — the exact under-specified-source-list bug this page names.DrillSolve · MediumDrillSolve · Medium
- Composition API · watch & Composableswatch and watchEffect anchor this whole quiz set — a direct check on whether the distinction actually stuck.QuizTake · MediumQuizTake · Medium
Read next
Computed Properties: Cached, Derived State
A computed only recalculates when a dependency it read actually changes — the caching a plain method call can never give you.
3 min
Composables: Reusable Stateful Logic
A composable is a plain function using ref, computed and watch — Vue's answer to a custom React hook, without the rules-of-hooks constraints.
3 min