patternjavascriptvueModerate
Vue 3 computed vs watch — choosing the right reactive primitive
Viewed 0 times
Vue 3.0+
computedwatchwatchEffectvue3 reactivityderived stateside effects
Problem
Using watch() where computed() should be used, or vice versa, leads to either stale derived values or excessive side-effect complexity. Developers use watch with a setter when computed with a setter is cleaner.
Solution
Use computed() for derived values. Use watch() for side effects triggered by state changes:
import { ref, computed, watch, watchEffect } from 'vue';
const firstName = ref('Alice');
const lastName = ref('Smith');
// computed: synchronous derived value — cached, efficient
const fullName = computed(() =>
// computed with setter (two-way binding)
const fullNameWritable = computed({
get: () =>
set: (val) => {
[firstName.value, lastName.value] = val.split(' ');
}
});
// watch: side effects when specific sources change
const userId = ref(1);
watch(userId, async (newId, oldId) => {
userData.value = await fetchUser(newId);
}, { immediate: true });
// watchEffect: auto-tracks all reactive deps used inside
watchEffect(async () => {
// automatically tracks userId.value
userData.value = await fetchUser(userId.value);
});
import { ref, computed, watch, watchEffect } from 'vue';
const firstName = ref('Alice');
const lastName = ref('Smith');
// computed: synchronous derived value — cached, efficient
const fullName = computed(() =>
${firstName.value} ${lastName.value});// computed with setter (two-way binding)
const fullNameWritable = computed({
get: () =>
${firstName.value} ${lastName.value},set: (val) => {
[firstName.value, lastName.value] = val.split(' ');
}
});
// watch: side effects when specific sources change
const userId = ref(1);
watch(userId, async (newId, oldId) => {
userData.value = await fetchUser(newId);
}, { immediate: true });
// watchEffect: auto-tracks all reactive deps used inside
watchEffect(async () => {
// automatically tracks userId.value
userData.value = await fetchUser(userId.value);
});
Why
computed() caches its result and only recomputes when dependencies change — it is synchronous and returns a value. watch() is for async operations, DOM manipulation, and explicit side effects. Misusing watch() for derived values creates race conditions and stale data.
Gotchas
- computed() cannot be async — use watchEffect for async derived state
- watch() is lazy by default — add { immediate: true } to run on mount
- watchEffect() runs immediately and tracks dependencies automatically but gives no oldValue
- watch() with a getter function: watch(() => obj.prop, cb) tracks obj.prop specifically
Code Snippets
Watching a nested property with a getter
// Watch deep nested object
const settings = reactive({ theme: { color: 'blue' } });
watch(
() => settings.theme.color,
(newColor) => applyTheme(newColor)
);Context
When deciding between computed and watch in Vue 3 Composition API
Revisions (0)
No revisions yet.