skip to content

In Vue 3, why use computed() for a cart total instead of a method called from the template?

level: juniorimportance: must knowfreq 80%

answer

  1. one runs per render, one does not
  2. cached until something it read changes
  3. only reactive reads count
  4. Date.now() inside a getter

basics

~20 s

computed() caches its result and re-runs its getter only after a reactive value it read has changed; a method called in the template has no cache and runs again on every re-render of the component.

solid answer

~40 s

`computed()` returns a readonly ref whose getter Vue tracks: every reactive value the getter reads becomes a dependency, and the cached result is reused until one of those dependencies changes. A method such as `cartTotal()` called from the template has no cache, so it runs on every render of the component, including renders caused by unrelated state such as an open dropdown. For a total that loops over every line item, and for further computeds built on that total, the cache saves repeated work. The caching cuts both ways: only *reactive* reads count, so `computed(() => Date.now())` never updates. Use `computed` for a pure derivation of reactive state; use a method when you need an argument or deliberately want a fresh call each time.

code

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

const items = ref([{ price: 49, qty: 1 }, { price: 9, qty: 3 }])
const open = ref(false)

const total = computed(() =>
  items.value.reduce((sum, i) => sum + i.price * i.qty, 0)
)

function totalByMethod() {
  return items.value.reduce((sum, i) => sum + i.price * i.qty, 0)
}
</script>

<template>
  <button @click="open = !open">Toggle</button>
  <p>{{ total }}</p>
  <p>{{ totalByMethod() }}</p>
</template>

go deeper

for a junior

Say that a computed is cached by its reactive dependencies and a template method call runs on every render, and give one example where that difference matters.

for a middle

Explain how the dependency set is recorded from reactive reads during the getter, why non-reactive reads such as Date.now() never invalidate it, and why the getter must be pure.

for a senior

Show where caching pays off in a real component, such as chains of derived totals, and when a method or a keyed map is the honest choice for argument-dependent values.

for a principal

Frame the choice as a team convention: derived state lives in computeds, side effects in watchers, and review flags template method calls that loop over large collections.

## What computed() gives you In Vue 3, `computed()` (imported from `vue`) takes a **getter function** and returns a **computed ref**: a readonly ref whose `.value` is whatever the getter last returned. In a `<script setup>` component you read it as `total.value` in script and as `total` in the template, where refs are unwrapped automatically. The important part is not the ref, it is the **cache**. While the getter runs, Vue records every reactive value it reads (a `ref`'s `.value`, a property of a `reactive()` object, another computed). Those reads become the computed's **dependencies**. Until one of them changes, reading `.value` returns the stored result without calling the getter again. ## Computed versus a method in the template A method is just a function. If the template contains `{{ cartTotal() }}`, the call is part of the component's render, and Vue re-runs the render whenever any reactive state the render reads changes. Every such render calls the method again, even when the cart items did not change. | | `computed()` | method called in the template | |---|---|---| | When the logic runs | after a dependency changed, on the next read | on every render of the component | | Result reused between renders | yes, cached | no | | Takes arguments | no | yes | | Other code can build on it | yes, other computeds and watchers can read it | only by calling it again | | Good for | pure derivations of reactive state | per-call work, values that need arguments | For a cart, the difference is concrete: - The component also holds unrelated state (a promo-code input, an open dropdown, a hover flag). - Each keystroke in the promo input re-renders the component. - With a method, every keystroke re-sums every line item. - With `computed`, the sum runs once per change to the items and is reused otherwise. The saving compounds when other derived values build on the first one: `tax`, `shipping` and `grandTotal` computeds all read `subtotal`, and none of them re-runs while the items are unchanged. ## Where the cache surprises people The cache is keyed by **reactive** reads only. Three consequences follow: 1. `computed(() => Date.now())` never updates, because `Date.now()` is not reactive; nothing can invalidate the cached value. Store the time in a ref that a timer updates instead. 2. A plain `let` variable or a non-reactive module constant read by the getter is not a dependency. Changing it does not refresh the computed. 3. Dependencies are collected on each run, so a branch that was not taken (`if (!showTax.value) return base`) does not track the reads inside it until a later run takes that branch. ## The getter must stay pure Because Vue decides *when* a getter runs, the getter must not do anything whose timing matters. The Vue documentation is explicit: a computed getter should only compute and return a value. It should not mutate other state, start an async request, or touch the DOM. Those are **side effects**, and they belong in a watcher or an event handler. Likewise, treat the value a computed returns as read-only: to change a derived list, change the source state it is derived from. ## When a method is still the right tool - The logic needs an argument, such as the subtotal of one category: a computed getter takes no caller arguments. - You deliberately want a fresh result on every call, for example a random pick or the current time at the moment of rendering. - The work is trivial and reads nothing worth caching. ## A worked example ```vue <script setup> import { ref, computed } from 'vue' const items = ref([ { name: 'Keyboard', price: 49, qty: 1 }, { name: 'Cable', price: 9, qty: 3 } ]) const promo = ref('') // Re-runs only when items (or an item's price/qty) change const subtotal = computed(() => items.value.reduce((sum, i) => sum + i.price * i.qty, 0) ) // A method: runs on every render, which is fine for cheap formatting function money(n) { return n.toFixed(2) } </script> <template> <input v-model="promo" /> <p>Subtotal: {{ money(subtotal) }}</p> </template> ``` Typing in the promo field re-renders the component and calls `money()` again, but `subtotal` returns its cached number without looping over the items.

  • Can a Vue computed take an argument, such as the subtotal for one category?
    No. The getter receives no caller arguments (since 3.4 it receives only its own previous value). Returning a function from a computed gives you something callable, but only the function is cached, not its results, so each call recomputes. Either compute a map of subtotals keyed by category in one computed, or use a method when an argument is genuinely needed.
  • Why does computed(() => Date.now()) show the same time forever?
    The cache is invalidated only by reactive dependencies, and `Date.now()` is not reactive, so after the first evaluation nothing can mark the value stale. Keep the current time in a `ref` that a timer updates, and derive from that ref instead.
  • What must never happen inside a Vue computed getter?
    Side effects: mutating other state, starting an async request, or changing the DOM. Vue decides when the getter runs, so any effect inside it runs at unpredictable times or not at all. Put side effects in a watcher or an event handler and keep the getter a pure derivation.

saying these in an interview costs you the question

  • A computed and a method run equally often; the difference is only syntax.
  • A computed re-evaluates on every render of the component.
  • computed(() => Date.now()) keeps updating as time passes.
  • Fetching data or setting another ref inside a computed getter is fine.
  • A computed needs a dependency list to know when to recompute.