codejump

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

Waiting at the end of this page.

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:

JavaScript
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

JavaScript
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

JavaScript
// 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 drills

Reading this page is the cheap half. These are the exercises that make you use it.