Fetching on input changes, syncing to localStorage and talking to non-Vue code all need watchers.
Computeds derive values; watchers run side effects in response to changes.
`watch(source, callback, options)` watches an explicit source: a ref, a reactive object, a getter function, or an array of these. The callback receives (newValue, oldValue, onCleanup).
- To watch a property of a reactive object or a destructured prop, pass a getter:
watch(() => props.id, ...)orwatch(() => id, ...). Passingstate.countdirectly passes a plain number and does not work. { immediate: true }runs the callback once right away;{ once: true }runs it only on the first change.- Watching a reactive object is deep automatically; for a ref holding an object, add
{ deep: true }(or a number to limit depth).
`watchEffect(fn)` runs immediately and re-runs whenever any reactive value it read changes, with no explicit source list. It is concise, but its dependencies are implicit and only those read synchronously are tracked.
Async watchers must handle stale results: if the id changes twice quickly, the first request may finish last. Vue 3.5 adds onWatcherCleanup(fn), called before the next run and on stop; use it to abort the old request.
By default callbacks run before the component updates; use { flush: 'post' } (or watchPostEffect) to see the updated DOM. Watchers created synchronously in setup stop with the component; both functions return a stop handle for manual control.
Example
import { ref, watch, watchEffect, onWatcherCleanup } from 'vue'
const userId = ref(1)
const user = ref(null)
watch(userId, async (id, oldId) => {
const controller = new AbortController()
onWatcherCleanup(() => controller.abort()) // runs if id changes again
try {
const res = await fetch(`/api/users/${id}`, { signal: controller.signal })
user.value = await res.json()
} catch (e) {
if (e.name !== 'AbortError') throw e
}
}, { immediate: true })
const theme = ref(localStorage.getItem('theme') ?? 'light')
watchEffect(() => {
localStorage.setItem('theme', theme.value) // re-runs when theme changes
document.documentElement.dataset.theme = theme.value
})Remember
watch(source, cb)for explicit sources; use a getter for reactive properties and propswatchEffecttracks whatever it reads synchronously and runs immediately- Abort stale async work with
onWatcherCleanup(3.5+) or the onCleanup argument
Watch out for
watch(state.count, ...)passes a number, not a source; usewatch(() => state.count, ...).- Using a watcher to copy derived data into another ref instead of a computed.