This content originally appeared on DEV Community and was authored by Bidisha Das
In Vue 2, computed properties and watch functions are key tools in the reactivity system. Understanding their internals and appropriate use cases is crucial to writing efficient, predictable applications.
Vue 2 Reactivity System (Brief Primer)
Vue 2 uses getter/setter interception via Object.defineProperty to make data reactive. When a component renders or computes a value, Vue tracks which properties were accessed. If those properties change, Vue knows which components or functions to re-run.
This tracking mechanism powers both computed and watch.
computed: Lazy, Cached Derivations
Purpose
To derive state from other reactive properties without side effects, and cache the result until a dependency changes.
Internal Behavior
- Lazily evaluated: only recalculates when accessed.
- Memoized: tracks dependencies and only updates when they change.
- Purity enforced: should have no side effects or async logic.
Prototype Note
Computed properties are defined on the component’s prototype, so they don’t appear as own properties:
vm.hasOwnProperty('computedProp') // false
Arguments Are Not Allowed
computed: {
val(flag) { ... } // ❌ Not allowed
}
If you need to pass arguments, use a method instead:
methods: {
val(flag) {
return flag === 1
? this.someDataProperty * this.otherVariable
: this.someDataProperty * 5;
}
}
Example: Basic Sum
export default {
data() {
return {
a: 2,
b: 3
};
},
computed: {
sum() {
console.log('Computed sum');
return this.a + this.b;
}
}
}
Real-World Example: Product Price Calculation
computed: {
finalPrice() {
return this.basePrice * (1 + this.taxRate) - this.discount;
}
}
Real-World Example: Reducing Expression Length
computed: {
deeplyNestedValue() {
return this.$store.state.module.submodule.deepValue;
}
}
This simplifies access and ensures reactivity when store updates.
Points to remember
Try to avoid mutating computed property as much as possible. Instead of mutating the computed property, you should try to update the data field which triggers new computations and invokes the getter.
A computed property behaves like a pure function: it always returns the same output for the same input, and it only re-evaluates when one of its reactive dependencies changes.
In contrast, a method is a regular function that can be called any number of times—regardless of whether its dependencies have changed. It's not cached, doesn't need to be pure, and doesn't create any new reactive data on its own.
watch: Reactive Side Effects
Purpose
To observe changes to specific reactive properties and trigger imperative code, including side effects or async logic.
Internal Behavior
- Eager: runs handler when dependencies change.
- Can be async.
-
Supports
deepandimmediateoptions.
Arguments
The handler receives the new and old value:
watch: {
myVar(newVal, oldVal) {
console.log('changed from', oldVal, 'to', newVal);
}
}
Real-World Example: Async API on Property Change
watch: {
userId: {
immediate: true,
handler(newVal) {
this.fetchUser(newVal);
}
}
},
methods: {
async fetchUser(id) {
const res = await fetch(`/api/users/${id}`);
this.userData = await res.json();
}
}
Real-World Example: Debounced Search API
watch: {
searchQuery(newQuery) {
clearTimeout(this._searchTimeout);
this._searchTimeout = setTimeout(() => {
this.fetchSearchResults(newQuery);
}, 300);
}
}
Real-World Example: Reacting to Deep Object
watch: {
settings: {
handler(newVal) {
console.log('Settings updated:', newVal);
},
deep: true
}
}
Real-World Example: Routing on Condition
watch: {
somethingSelected(val) {
if (val) this.$router.push('/next-step');
}
}
Real-World Example: Fetch Data on Filter Change
watch: {
filters: {
handler(newFilters) {
this.loadTableData(newFilters);
},
deep: true
}
},
methods: {
async loadTableData(filters) {
const query = new URLSearchParams(filters).toString();
const res = await fetch(`/api/data?${query}`);
this.tableData = await res.json();
}
}
Comparison Table
| Feature | computed |
watch |
|---|---|---|
| Trigger | Lazy (on access) | Eager (on mutation) |
| Purpose | Derived value | Side effects / async |
| Caching | Yes | No |
| Async Support | No | Yes |
| Side Effects | No (should be pure) | Yes |
| Deep/Nested Support | No | Yes (via deep: true) |
| Arguments | ❌ Not allowed | ✅ newVal, oldVal |
React Equivalents
computed → useMemo
const sum = useMemo(() => {
console.log("Computing sum");
return a + b;
}, [a, b]);
watch → useEffect
useEffect(() => {
async function loadUser() {
const res = await fetch(`/api/users/${userId}`);
const data = await res.json();
setUserData(data);
}
loadUser();
}, [userId]);
Deep Watch in React (manual)
React doesn't support deep watching natively:
useEffect(() => {
console.log("Settings changed", settings);
}, [settings.theme, settings.notifications]);
When to Use What
Adapted from Flavio Copes' best practices:
Use methods:
- To respond to DOM events (e.g.,
@click) - When you need to pass arguments
- To call imperative logic on user action
Use computed:
- To create derived state from reactive sources
- When using nested data in the template
- When a value should update reactively without manual tracking
Use watch:
- To trigger side effects (e.g., route change, fetch data)
- To observe prop changes
- To respond to a single property reaching a specific value
Anti-Patterns
❌ Using watch for derivation
watch: {
a(val) {
this.sum = val + this.b;
},
b(val) {
this.sum = this.a + val;
}
}
Better:
computed: {
sum() {
return this.a + this.b;
}
}
❌ Side effects in computed
computed: {
fullName() {
fetch('/api/profile'); // ❌ side effect
return this.first + ' ' + this.last;
}
}
Summary
-
Use
computedfor pure derivations that should be cached. -
Use
watchfor executing logic when data changes, especially async effects. -
In React, replicate:
-
computedwithuseMemo -
watchwithuseEffect
-
Vue 2's split between computed and watch promotes clear separation of declarative derivation vs imperative side effects, a pattern React replicates using different primitives.
Need live examples, benchmarking, or async control patterns (e.g., cancellation with watch)? Ask and I can break that down next.
This content originally appeared on DEV Community and was authored by Bidisha Das
Bidisha Das | Sciencx (2025-11-09T10:12:51+00:00) Computed and Watchers in Vue: A Deep Dive. Retrieved from https://www.scien.cx/2025/11/09/computed-and-watchers-in-vue-a-deep-dive/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.
