Back

How Computed Properties and Watchers Work in Vue.js

How Computed Properties and Watchers Work in Vue.js

Vue’s reactivity system automatically tracks dependencies and updates your UI when data changes. But knowing when to use Vue 3 computed properties versus Vue 3 watchers can make the difference between clean, performant code and a maintenance nightmare. Let’s explore how these features work and when each one shines.

Key Takeaways

  • Computed properties are cached, declarative functions that derive new values from reactive data
  • Watchers handle side effects like API calls and are perfect for asynchronous operations
  • Use computed for pure transformations, watchers for imperative logic with side effects
  • Computed properties only recalculate when dependencies change, improving performance

Understanding Vue’s Reactivity Foundation

Vue 3’s reactivity system uses Proxies to track when properties are accessed and modified. When reactive data changes, Vue knows exactly which parts of your application depend on that data and updates them efficiently. Computed properties and watchers are two powerful tools built on this foundation, each serving distinct purposes in your reactive workflow.

Vue 3 Computed Properties: Declarative and Cached

Computed properties derive new reactive values from existing data. They’re declarative, cached, and should always be pure functions without side effects.

Composition API Syntax

<script setup>
import { ref, computed } from 'vue'

const price = ref(100)
const quantity = ref(2)

const total = computed(() => price.value * quantity.value)
const formattedTotal = computed(() => `$${total.value.toFixed(2)}`)
</script>

<template>
  <p>Total: {{ formattedTotal }}</p>
</template>

Options API Syntax

export default {
  data() {
    return {
      price: 100,
      quantity: 2
    }
  },
  computed: {
    total() {
      return this.price * this.quantity
    },
    formattedTotal() {
      return `$${this.total.toFixed(2)}`
    }
  }
}

The key advantage? Caching. Vue only recalculates a computed property when its dependencies change. If you reference formattedTotal multiple times in your template, the calculation runs once per update cycle, not on every access.

Writable Computed Properties

Occasionally, you need a computed property with both getter and setter:

import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  get() {
    return `${firstName.value} ${lastName.value}`
  },
  set(newValue) {
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})

Vue 3 Watchers: Imperative Side Effects

Watchers observe reactive values and perform side effects when they change. Unlike computed properties, watchers are imperative and perfect for asynchronous operations, API calls, or DOM manipulations.

Basic Watch Syntax

// Composition API
import { ref, watch } from 'vue'

const searchQuery = ref('')
const results = ref([])

watch(searchQuery, async (newQuery) => {
  if (newQuery.length > 2) {
    results.value = await fetchSearchResults(newQuery)
  }
})

watchEffect for Automatic Tracking

watchEffect automatically tracks dependencies without explicitly declaring them:

import { ref, watchEffect } from 'vue'

const searchQuery = ref('')
const results = ref([])

watchEffect(async () => {
  // Automatically tracks searchQuery.value
  if (searchQuery.value.length > 2) {
    results.value = await fetchSearchResults(searchQuery.value)
  }
})

Advanced Watcher Options

watch(source, callback, {
  immediate: true,  // Run immediately on creation
  deep: true,       // Watch nested properties
  flush: 'post'     // Run after DOM updates
})

Computed vs Watch: Making the Right Choice

Here’s when to use each approach:

Use Computed Properties when:

  • Deriving new values from existing reactive data
  • You need the result in your template
  • The operation is synchronous and pure
  • You want automatic caching

Use Watchers when:

  • Performing side effects (API calls, logging, localStorage)
  • Reacting to data changes with imperative logic
  • Handling asynchronous operations
  • You need access to both old and new values

Practical Comparison

// ❌ Wrong: Side effect in computed
const searchResults = computed(async () => {
  const response = await fetch(`/api/search?q=${query.value}`)
  return response.json() // Won't work - computed must be synchronous
})

// ✅ Right: Watcher for side effects
watch(query, async (newQuery) => {
  const response = await fetch(`/api/search?q=${newQuery}`)
  searchResults.value = await response.json()
})

// ✅ Right: Computed for pure derivation
const filteredResults = computed(() => 
  searchResults.value.filter(item => item.active)
)

Performance Considerations

Computed properties excel at performance through caching. They only recalculate when dependencies change, making them ideal for expensive operations:

// This only recalculates when items or filterText change
const expensiveFilter = computed(() => {
  console.log('Filtering...') // Runs only on dependency change
  return items.value.filter(item => 
    complexFilterLogic(item, filterText.value)
  )
})

Watchers, however, always execute when triggered. Use them judiciously, especially with deep: true on large objects.

Conclusion

Vue 3 computed properties and watchers remain fundamental to Vue’s reactivity system. Remember the simple rule: use computed for pure, cached derivations and watchers for side effects. Computed properties keep your templates clean and performant through automatic caching, while watchers handle the imperative, asynchronous operations that computed properties can’t. Master both, and you’ll write Vue applications that are both elegant and efficient.

FAQs

No, computed properties must be synchronous and return values immediately. For asynchronous operations, use watchers or methods instead. Computed properties are designed for pure transformations of reactive data.

Use watchEffect when you want automatic dependency tracking and don't need the old value. Use watch when you need explicit control over what to watch, access to old values, or specific configuration options like deep watching.

Yes, computed properties track changes to arrays and objects. However, for deep reactivity with nested properties, ensure your source data is properly made reactive using ref or reactive. Vue 3 handles this better than Vue 2.

A computed property calculates only once per render cycle, regardless of how many times it's referenced. This caching behavior makes computed properties efficient for expensive calculations used in multiple places.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay