Computed Properties or Watchers?
Overview
When working with Vue.js, you will often need to react to data changes. Vue provides two main solutions for this:
- Computed properties
- Watchers
While they may seem similar at first, they have different purposes and uses.
Overview of differences
Computed properties
- Purpose: Create new values from existing reactive data.
- Return value: Will always return a value.
- Caching: Cached automatically based on dependencies.
- Template access: Accessible in templates
- Side effects: Does not produce side effects and will ony re-evaluate when a reactive dependency is updated.
Watchers
- Purpose: Performs side effects when data changes, such as updating the DOM or reactive state.
- Return value: No return values.
- Caching: Not automatically cached.
- Template access: Not directly accessible in the template.
- Side effects: Designed for side effects such as updating the DOM or state.
When to use computed properties
Computed properties are useful for:
- Calculating values from existing reactive data.
- Caching values to performance.
- Watching multiple reactive sources.
- Accessing results directly in templates.
- Extracting JavaScript expressions out of the template syntax (
{{ }}
).
Example: full name computation
<script setup>
import { ref, computed } from 'vue'
const first = ref('Homer')
const last = ref('Simpson')
const fullName = computed(() => {
return `${first.value} ${last.value}`
})
</script>
<template>
<p>Welcome, {{ fullName }}!</p>
</template>
When to use watchers
Watchers are great for:
- Performing side effects (API calls, working with the DOM, etc.)
- Asynchronous operations.
- Reacting to data changes.
- Updating external state.
Example: Fetching data from an API
<script setup>
import { ref, watch } from 'vue'
const searchQuery = ref('')
const searchResults = ref([])
watch(searchQuery, (newQuery, oldQuery) => {
if (newQuery !== oldQuery) {
getSearchResults(newQuery)
}
})
const getSearchResults = async (query) => {
// make api call and update searchResults.value
}
</script>
Performance considerations
Computed properties
- Cached by default: Only re-calculates when reactive dependencies change.
- Efficient: Perfect for expensive calculations.
- Reactive: Auto updates when a dependency is updated.
Watchers
- Not cached: Runs every time the watched value changes.
- More control: Custom caching logic can be implemented if required.
- Immediate execution: Runs immediately when reactive data is updated.
Common mistakes
Don't use computed properties for:
- Manipulating the DOM.
- Side effects.
- Async tasks.
Don't use watchers for:
- When you need the result in the template.
- calculating values to render in the template.
- Simple value calculations.
Summary
Usage | Computed Properties | Watchers |
---|---|---|
Purpose | Calculate values | Perform side effects |
Multiple Sources | Yes | Needs multiple watchers |
Caching | Automatic | Manual (if required) |
Return Value | Always returns a value | No return value |
Template Access | Yes | No |
Async Operations | No | Yes |
Shopping cart example
This example simulates a shopping cart, using both computed
and watch
:
<script setup>
import { ref, computed, watch } from 'vue'
const items = ref([
{ name: "apple", price: 1.2 },
{ name: "banana", price: 2.3 },
]);
const taxRate = ref(0.2);
// Computed - used to calculate values
const subtotal = computed(() => {
return items.value.reduce((sum, item) => sum + item.price, 0);
});
const tax = computed(() => {
return subtotal.value * taxRate.value;
});
const total = computed(() => {
return subtotal.value + tax.value;
});
function addItem(item) {
items.value.push(item);
}
// Watcher - used to save to localStorage
watch(
items,
(newItems) => {
localStorage.setItem("basket", JSON.stringify(newItems));
},
{ deep: true }
);
</script>
And add an item in the template:
<button @click="addItem({ name: 'pear', price: 3.4 })">Add Item</button>
This example shows computed properties for calculating values (subtotal, tax, total) and a watcher for saving to the browsers localStorage
(side effect).