Understanding Watchers in Vue 3
Vue 3 watchers let you run side-effect logic when reactive data changes. They are useful for tasks such as triggering an API request, synchronizing with an external system, persisting state, or responding to a transition between values.
1. What Is a Watcher?
A watcher observes one or more reactive sources and runs a callback when their values change. Unlike a computed property, which represents derived state, a watcher is usually intended for side effects.
2. Basic Watcher
<template>
<input v-model="name" placeholder="Enter your name" />
<p>Your name is: {{ name }}</p>
</template>
<script setup>
import { ref, watch } from 'vue';
const name = ref('');
watch(name, (newValue, oldValue) => {
console.log(`Name changed from ${oldValue} to ${newValue}`);
});
</script>The first argument is the reactive source and the second is the callback. For a simple ref, the callback receives the new and previous values.
3. Watchers vs. Computed Properties
| Feature | Watcher | Computed Property |
|---|---|---|
| Purpose | Run side effects after state changes | Derive a value from reactive state |
| Typical uses | API calls, persistence, async work | Calculations and display values |
| Result | Callback behavior | Cached reactive value |
If you only need a value calculated from other state, prefer computed(). Use watch() when something needs to happen because a value changed.
4. deep and immediate Options
Use deep when you need to react to nested mutations within an observed object structure:
<script setup>
import { reactive, watch } from 'vue';
const user = reactive({ profile: { name: 'John', age: 30 } });
watch(
() => user.profile,
(newVal) => console.log('Profile updated:', newVal),
{ deep: true }
);
</script>Use immediate when the callback should run once as soon as the watcher is created:
<script setup>
import { ref, watch } from 'vue';
const counter = ref(0);
watch(
counter,
(newVal) => console.log('Watcher triggered:', newVal),
{ immediate: true }
);
</script>5. Watch Refs and Reactive Object Properties
<script setup>
import { ref, reactive, watch } from 'vue';
const counter = ref(0);
const state = reactive({ name: 'Alice' });
watch(counter, (newVal) => console.log('Counter updated:', newVal));
watch(() => state.name, (newVal) => console.log('Name updated:', newVal));
</script>For a property on a reactive object, pass a getter function such as () => state.name so Vue can track the intended source.
6. Best Practices
- Use a computed property instead when the goal is only to derive data.
- Avoid broad deep watchers when a narrower source can express the same intent.
- Clean up resources created inside watcher callbacks, especially timers, subscriptions, and async work.
- Use watcher cleanup APIs or an abort mechanism to prevent stale asynchronous requests from updating newer state.
7. Common Mistakes
- Passing a non-reactive primitive value instead of a ref or getter.
- Using deep watching for large object graphs without considering the cost.
- Starting asynchronous work on every change without cancellation or debouncing.
Conclusion
Watchers are a flexible part of Vue 3’s reactivity system. Use them when a reactive change should trigger an effect, and use computed properties when you simply need another reactive value derived from existing state.