Watchers - watch and watchEffect
Running side effects in response to reactive state changes, and the difference between watch and watchEffect.
2 menit membaca
computed() is for deriving a value from other state. Sometimes what you need instead is to run a side effect when state changes — logging, an API call, updating localStorage, showing a notification. That's what watch() and watchEffect() are for.
watch: explicit about what it's watching
<script setup>
import { ref, watch } from "vue";
const searchTerm = ref("");
watch(searchTerm, (newValue, oldValue) => {
console.log(`Search changed from "${oldValue}" to "${newValue}"`);
});
</script>watch() takes a source (a ref, or a function returning a value to track) and a callback that receives the new and old values. It only reacts to changes in that specific source — nothing else you reference inside the callback triggers it. This makes watch() a good fit when you want to react to one particular piece of state and need access to its previous value.
Watching a reactive object's property
Watching a single property of a reactive() object requires a getter function, not the property itself, because passing user.name directly would pass its current plain value rather than a reactive reference to watch:
<script setup>
import { reactive, watch } from "vue";
const user = reactive({ name: "Ada", age: 30 });
watch(
() => user.name,
(newName) => {
console.log(`Name changed to ${newName}`);
}
);
</script>watchEffect: automatically tracks its dependencies
watchEffect() takes just a function, runs it immediately, and automatically re-runs it whenever any reactive value it read during that run changes — no explicit source needed:
<script setup>
import { ref, watchEffect } from "vue";
const width = ref(window.innerWidth);
const height = ref(window.innerHeight);
watchEffect(() => {
console.log(`Viewport is ${width.value} x ${height.value}`);
});
</script>This re-runs whenever either width or height changes — you never had to list them. That convenience is also the trade-off: it's less obvious at a glance exactly what triggers it, since the dependencies are implicit in whatever the function happens to read, not declared upfront.
Which one to reach for
Use watchEffect() when you want to run a function immediately and keep it in sync with whatever it reads — it reads naturally for things like syncing to localStorage or updating a DOM property, where you don't especially care about the previous value. Use watch() when you need to:
- React to one specific, named source rather than "everything read inside this function."
- Compare the new value against the old one.
- Skip the first run —
watch()doesn't fire immediately by default (pass{ immediate: true }if you want it to).
<script setup>
import { ref, watch } from "vue";
const userId = ref(1);
watch(userId, async (id) => {
const response = await fetch(`/api/users/${id}`);
console.log(await response.json());
});
</script>A common real use is exactly this: watching an ID and re-fetching data whenever it changes, which is far more explicit with watch() than trying to infer it from a watchEffect().