← Blog
15 Sept 2026vue computed propertiesvue reactivitycomposition apivue performancedom studio

Vue Computed Properties: Caching, APIs, and Performance

Master Vue computed properties with practical examples for Options and Composition APIs. Learn caching mechanics, avoid performance pitfalls, and integrate

Vue Computed Properties: Caching, APIs, and Performance

A dashboard can feel perfectly responsive until a user starts typing into a filter field. Suddenly, a table recalculates, form controls update, and unrelated parts of the interface appear to work harder than they should. The problem often isn’t Vue’s rendering model itself. It’s the way derived state has been placed inside it.

Vue computed properties give you a disciplined way to express values that come from reactive state. They keep source data intact, expose a readable result to the template, and reuse that result until a tracked dependency changes. That makes them valuable in dashboards, forms, filtering interfaces, and headless component compositions, but only when the dependency graph remains manageable.

Table of Contents

Understanding Vue Computed Properties and Their Core Purpose

A computed property represents derived state. If items, query, and sortOrder are reactive sources, then visibleItems can be calculated from them without becoming another source of truth.

const visibleItems = computed(() => {
  return items.value
    .filter(item => item.name.includes(query.value))
    .sort(compareBy(sortOrder.value))
})

The template consumes visibleItems as a value, not as an operation:

<ul>
  <li v-for="item in visibleItems" :key="item.id">
    {{ item.name }}
  </li>
</ul>

That distinction matters. The original collection remains authoritative, while the computed result describes how the interface should present it. A user changing a filter updates the reactive input. Vue then invalidates the derived result and the interface receives the new snapshot.

Keep source state separate from display state

A useful rule is to classify every value in a component:

  • Source state is stored directly, such as a selected filter, an API response, or a form field.
  • Derived state is calculated from source state, such as a filtered list, a full name, or a validation status.
  • Side effects cause something outside the calculation to happen, such as an API request, logging, or local storage writes.

Computed properties belong in the middle category. They shouldn’t mutate the source collection, start network requests, or modify unrelated refs. A getter that changes state while Vue is evaluating it can create difficult update cycles and make the result depend on evaluation order.

Practical rule: If you can describe a value as “the result of these reactive inputs”, start with a computed property. If you need an action to happen, use a method or watcher instead.

The same principle applies when a computed value feeds a component using v-model. The model should still have a clear owner, while the computed layer adapts the value for presentation or interaction. Vue teams working through that boundary can use this guide to understand v-model in Vue.

A computed property isn’t a storage mechanism, and it isn’t a replacement for every function. Its purpose is to make derived UI state explicit, reusable, and connected to the dependencies that determine it.

How Caching and Reactivity Work Under the Hood

A search field can update on every keystroke while a computed filter reads the query and a reactive collection. Vue evaluates that getter while tracking each reactive value it accesses. If the getter reads query.value and items.value, both become dependencies. Changing either one invalidates the cached result, and the next read evaluates the getter again.

A diagram illustrating the flow of caching and reactivity in web development for faster data updates.

Computed values are cached according to their dependencies. Repeated access reuses the existing result until one of those dependencies changes. The current Vue computed properties documentation presents computed properties as the API for expressing derived reactive logic. Older Vue documentation records that this caching behavior was introduced in version 0.12.8, replacing eager recalculation on every access with lazy, dependency-based evaluation.

Lazy evaluation is useful, but dependency collection still matters

Caching avoids repeating work after a value has been calculated. The getter still has a cost: Vue must collect the dependencies during evaluation and invalidate the result when those dependencies change.

A short getter over a focused ref is usually inexpensive. A getter that scans a large collection, touches many nested reactive objects, or chains through several other computed values creates a broader reactive graph. In a large component tree, that breadth can make invalidation and reevaluation harder to reason about. Caching reduces repeated calculation, but it does not make an unnecessarily wide dependency relationship free.

Keep computed getters narrow enough to reflect one derived concern. If a filter, sort, formatting step, and permission check all live in one getter, a change to any input can rerun the entire chain. Splitting those concerns can make both profiling and debugging clearer.

Why batching changes the result

Vue’s reactivity system batches invalidated effects asynchronously. If an event handler updates a search term and a selected category, Vue can consolidate the resulting work rather than render a meaningful interface state after each assignment. The Vue reactivity and computed watcher documentation describes this asynchronous batching behavior.

A computed filter depending on both values therefore participates in one batched update cycle. It does not need to produce separate UI results for each intermediate mutation.

Treat the getter as a pure, read-only description of state. Update the refs or reactive objects that provide its inputs, then let dependency tracking invalidate the derived value. Side effects, mutations, and broad data processing belong outside this calculation path, especially when a headless component library such as DOM Studio causes several reactive controls to update together.

Practical Implementation Across Options and Composition APIs

The Options API puts computed properties in a dedicated computed object. A getter can read component state through this, and the template consumes the property without calling it.

export default {
  data() {
    return {
      firstName: 'Ada',
      lastName: 'Lovelace'
    }
  },

  computed: {
    fullName() {
      return `${this.firstName} ${this.lastName}`
    }
  }
}

fullName is derived from firstName and lastName. It shouldn’t be assigned directly because those two data properties are the actual sources.

Writable computed values

A computed property is read-only by default. A writable version adds a setter that translates an assignment into updates to the underlying state. The current Vue documentation notes that writable computed properties are available in Vue 3.4 and later.

export default {
  data() {
    return {
      firstName: 'Ada',
      lastName: 'Lovelace'
    }
  },

  computed: {
    fullName: {
      get() {
        return `${this.firstName} ${this.lastName}`
      },
      set(value) {
        const [firstName, ...rest] = value.trim().split(/\s+/)
        this.firstName = firstName || ''
        this.lastName = rest.join(' ')
      }
    }
  }
}

This pattern can be appropriate when a component exposes a single conceptual value while storing it as separate fields. It becomes confusing when the setter performs unrelated work or hides a complicated state transition. In those cases, an explicit method often communicates intent better.

Composition API equivalents

The Composition API uses computed() with ref() or reactive().

<script setup lang="ts">
import { computed, ref } from 'vue'

const firstName = ref('Ada')
const lastName = ref('Lovelace')

const fullName = computed<string>(() => {
  return `${firstName.value} ${lastName.value}`
})
</script>

<template>
  <p>{{ fullName }}</p>
</template>

Inside JavaScript or TypeScript, refs use .value. Vue unwraps refs when they appear in templates, so {{ fullName }} is the idiomatic template form.

A writable Composition API computed value uses an object with get and set:

const fullName = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (value: string) => {
    const [first, ...rest] = value.trim().split(/\s+/)
    firstName.value = first || ''
    lastName.value = rest.join(' ')
  }
})

Keep the getter synchronous and predictable. If a calculation needs asynchronous work, expose loading and result state separately, then update that state from a watcher or an event-driven function.

Comparing Computed Properties with Methods and Watchers

The choice becomes clearer when you ask what the code is trying to produce. A computed property produces a value from reactive inputs. A method performs an operation when called. A watcher observes a change and runs an effect.

A method can accept arguments, which makes it useful for parameterised work:

function formatCurrency(amount, currency) {
  return new Intl.NumberFormat('en-GB', {
    style: 'currency',
    currency
  }).format(amount)
}

A computed property is a better fit when the same derived result appears in the template and depends on reactive state:

const filteredItems = computed(() => {
  return items.value.filter(item =>
    item.name.toLowerCase().includes(query.value.toLowerCase())
  )
})

Methods used during rendering run whenever the component renders. Vue doesn’t inspect the method body to determine whether its result depends on the state change that caused that render. That makes methods a poor default for expensive, repeated derivations.

Reactivity Tool Decision Matrix

Feature Computed Property Method Watcher
Primary purpose Return derived reactive state Perform an operation or calculate with arguments Run logic after a reactive value changes
Caching Caches until tracked dependencies change No computed-value cache Not applicable
Template usage Read as a property Call with parentheses Usually not rendered directly
Parameters Not naturally parameterised Supports parameters Receives changed values
Side effects Should remain free of side effects Can perform an action, although event handlers are usually clearer Designed for side effects
Asynchronous work Not the right tool for async workflows Can call async logic from an explicit event Suitable for requests, persistence, and coordination
Best example Filtered rows or validation state Format one supplied value Fetch suggestions when a query changes

A practical decision test

Use a computed property if the answer is a value that should stay synchronised with state. Use a method if a user action should trigger work or if the calculation needs arguments that vary per call. Use a watcher when the change itself is the event, especially when the response involves I/O, debouncing, persistence, or another side effect.

Don’t use a watcher to copy one derived value into another ref. That creates duplicated state and introduces timing concerns. A computed relationship usually expresses that dependency more directly and with less code.

Performance Pitfalls and Debugging Strategies

The phrase “computed is cached” can lead developers to treat computed properties as automatically cheap. That isn’t accurate. The cache reduces repeated getter execution, but Vue still tracks every reactive dependency the getter touches and still has to respond when those dependencies change.

A computed property that filters a broad reactive array may invalidate whenever the array changes, even if the visible portion of the interface doesn’t depend on the changed item. A getter that walks a nested reactive object can also create a wide dependency graph. Community analysis of computed properties and watchers discusses this trade-off, particularly for large arrays and complex nested objects, in this examination of Vue computed and watcher performance.

Reduce the graph before optimising the getter

Start by looking at the shape of the state, not by trying to make the getter clever.

  • Split unrelated concerns: Keep search controls, pagination state, and table records in separate refs where that reflects their actual ownership.
  • Avoid unnecessary deep reactivity: Don’t place large immutable payloads inside a reactive structure if the interface only needs a narrow projection.
  • Move expensive transforms deliberately: A data normalisation step may belong at the API boundary, in a store action, or in a worker rather than inside a frequently invalidated computed getter.
  • Keep dependencies narrow: Read the specific fields required for the result instead of passing an entire object through a chain of derived calculations.

Debug the invalidation path

Vue DevTools can help you inspect component updates and reactive state. Pair it with browser performance profiling and temporary logging inside the getter. The useful question isn’t “how long does this calculation take?” It is “which dependency changed, and did that change need to invalidate this result?”

Check for common mistakes:

  • A filter value is a plain variable instead of a ref, so the computed value never updates.
  • A getter assumes an object exists during the initial render, producing an undefined-property error.
  • Destructuring a reactive object removes the connection Vue was tracking.
  • A computed getter returns a fresh object or array on every invalidation, causing downstream consumers to see a new identity even when the meaningful content is unchanged.

The DOM Studio performance optimisation guidance can sit alongside this audit when you’re reviewing component structure and rendering cost. The library choice won’t fix an oversized dependency graph. State boundaries and update frequency still determine the work Vue must perform.

Real-World Patterns and DOM Studio Integration

Computed properties earn their place when the interface has a clear relationship between user input and displayed state. A data table might derive visible rows from a query, status filter, and sort selection. A multi-step form might derive whether the current step is complete from several fields. A settings panel might derive whether a control should be disabled from permissions and current values.

The pattern is consistent: keep raw inputs in refs or reactive objects, then expose focused computed values to the template.

const query = ref('')
const status = ref('active')
const records = ref<RecordItem[]>([])

const visibleRecords = computed(() => {
  const normalisedQuery = query.value.trim().toLowerCase()

  return records.value.filter(record => {
    const matchesQuery = record.name
      .toLowerCase()
      .includes(normalisedQuery)

    const matchesStatus = status.value === 'all' ||
      record.status === status.value

    return matchesQuery && matchesStatus
  })
})

That value can drive a table or list without mutating records. Validation follows the same approach:

const email = ref('')
const emailError = computed(() => {
  if (!email.value) return 'Email is required'
  return email.value.includes('@') ? '' : 'Enter a valid email address'
})

Use computed state as an adapter

Headless components are particularly compatible with this model because they separate stateful behaviour from visual markup. A computed value can provide a selected option, disabled status, label, or filtered collection while the component handles interaction details.

DOM Studio provides headless web component primitives with a Vue integration layer, including reactive props, v-model support, and slots. Its component model is designed to handle accessibility behaviour such as WAI-ARIA roles, focus management, keyboard interaction, and screen reader semantics, so a Vue computed property can concentrate on application state rather than duplicating those mechanics.

Screenshot from https://getdom.studio

For a dropdown, isDisabled might derive from permissions and form status. For a combobox, availableOptions can derive from the query and source collection. For tabs, activePanel can expose the panel associated with the current route or selection. The component receives a stable, meaningful value and remains responsible for its interaction contract.

When adopting web components inside Vue, pay attention to property binding and event conventions rather than assuming every custom element behaves like a native input. This overview of using web components in Vue is useful when deciding where computed values should connect to wrapper props, slots, and model bindings.

Migration Notes and Final Takeaways

Migration work is easier when you treat computed properties as an architectural boundary rather than a syntax change. Start by listing the source fields each legacy computed getter reads, then identify whether the getter is pure, whether it returns a stable conceptual value, and whether its setter is doing more than translating input.

Vue 2 Options API computed properties generally map cleanly to Vue 3 Options API code. The same getter and setter structure remains familiar, while Composition API code moves the relationship into explicit refs and computed() calls.

A practical migration sequence

  1. Move source state first. Convert data fields into ref() or reactive() values before rewriting derived expressions.
  2. Translate access deliberately. Composition API code uses .value in JavaScript and TypeScript, while templates unwrap refs automatically.
  3. Replace legacy cache assumptions. Don’t carry forward old cache: false expectations as a performance strategy. Decide whether the value is derived, then choose a method or explicit recalculation when caching isn’t appropriate.
  4. Review setters. A writable computed property should update its underlying source fields. If it performs requests, notifications, or unrelated mutations, move those effects into an explicit action.
  5. Audit mixins. Mixins can hide which fields a computed getter depends on and can create naming collisions. Composables make dependencies visible and easier to test, but they still need focused state boundaries.
  6. Test missing inputs. Props and asynchronous data may be unavailable during the initial render. Guard computed getters where a null or undefined value is valid.

The current Vue guidance records that writable computed properties exist in Vue 3.4 and later, so teams supporting earlier Vue 3 releases should check their target version before relying on that API form. The relevant reference remains the official Vue computed properties guide.

An infographic titled Migration Notes and Final Takeaways presenting essential steps for a successful system migration process.

Keep these production rules close:

  • Pure getters: Return a result without changing reactive state or causing external effects.
  • Narrow dependencies: Track the smallest meaningful set of refs and properties.
  • Correct tool selection: Use methods for actions and parameters, watchers for effects, and computed properties for synchronous derived values.
  • Defensive calculations: Handle incomplete props, nullable fields, and empty collections intentionally.
  • Measured optimisation: Confirm invalidation and rendering problems with DevTools and profiling before restructuring code.

Computed properties remain one of Vue’s clearest ways to express UI state. Their performance comes from the relationship between caching, dependency tracking, and batching, so the quality of that relationship depends on how carefully the surrounding state is organised.


DOM Studio offers headless, Vue-ready components with reactive props, v-model support, slots, and built-in accessibility behaviours for menus, dialogs, tabs, comboboxes, and other interface primitives. Visit DOM Studio to connect focused computed state to reusable interaction patterns without rebuilding keyboard and WAI-ARIA handling in every component.