computed

computed derives a cached value from other state, re-running its getter only when a dependency changes.

Syntaxcomputed(() => value) or computed({ get: () => value, set: (v) => {} })

A computed is a value derived from other reactive state. You pass computed a getter and get back a ref: read it as .value in script and plainly in the template. Vue records what the getter read, and recalculates only when one of those things changes.

Cached, which is the whole point

A computed keeps its last result. Read it ten times in one render and the getter runs once. A method called from the template runs on every re-render, even when nothing it reads has changed. So derived values belong in a computed; work that must happen on each call belongs in a function.

Caching also means the getter must be pure: no requests, no mutating other state, nothing time-dependent. Date.now() in a getter is a bug, because Vue has no dependency that would invalidate the cache.

Writable computeds

Pass { get, set } instead of a function and the computed accepts writes. The setter writes to the underlying state and the getter then recalculates from it. Use it for one field that is really two, such as a full name over a first and last name.

What trips people up

Mutating the result. A getter-only computed is readonly in the type system, and assigning to its .value logs a warning in the dev build and is ignored. Mutating the object or array the getter handed back is a different matter: nothing blocks it at runtime, so the write lands on the source state or is silently thrown away the next time the getter runs. Treat a computed result as read-only and update the state it derives from instead. Since Vue 3.4 the getter also receives the previous value as its first argument, which is how you clamp or hold a high-water mark.

Example

Example · vue
<!-- src/components/TaskList.vue -->
<script setup lang="ts">
import { computed, ref } from 'vue'

interface Task {
  id: number
  title: string
  done: boolean
}

const tasks = ref<Task[]>([
  { id: 1, title: 'Write the getter', done: true },
  { id: 2, title: 'Read the cache', done: false }
])
const hideDone = ref(false)

const visible = computed<Task[]>(() =>
  hideDone.value ? tasks.value.filter((task) => !task.done) : tasks.value
)
const remaining = computed(() => tasks.value.filter((task) => !task.done).length)

function toggleHide(): void {
  hideDone.value = !hideDone.value
}
</script>

<template>
  <button type="button" @click="toggleHide">
    {{ hideDone ? 'show all' : 'hide done' }}
  </button>
  <p>{{ remaining }} left</p>
  <ul>
    <li v-for="task in visible" :key="task.id">{{ task.title }}</li>
  </ul>
</template>

<style scoped>
li {
  list-style: none;
}
</style>

When to use it

  • Filter or sort a list for display without touching the stored data.
  • Show a derived label or total that several parts of the template read.
  • Expose one editable field that writes back to two pieces of state.

More examples

A writable computed

The two-argument object form, which turns a derived value into something you can also assign to. The setter must write to real state; it cannot store a value of its own. This is what you want when one input edits two fields, and it is also how a component exposes a single editable value over a more structured state.

Example · ts
// src/lib/full-name.ts
import { computed, ref } from 'vue'

const first = ref('Grace')
const last = ref('Hopper')

const fullName = computed<string>({
  get: () => `${first.value} ${last.value}`,
  set: (value: string) => {
    const [head = '', ...rest] = value.split(' ')
    first.value = head
    last.value = rest.join(' ')
  }
})

fullName.value = 'Ada Lovelace'
console.log(first.value, last.value)
// Output: Ada Lovelace

Cached, next to the uncached version

Two reads, one run of the getter; a write to price invalidates the cache and the next read runs it again. The counter here exists only to make the cache visible, and a real getter has no side effects. Compare with a plain function doubled(): it would run on every read and every re-render.

Example · ts
// src/lib/cache-demo.ts
import { computed, ref } from 'vue'

const price = ref(100)
let runs = 0

const doubled = computed(() => {
  runs += 1 // counting only, to show the cache
  return price.value * 2
})

console.log(doubled.value, doubled.value, runs)
// Output: 200 200 1

price.value = 200
console.log(doubled.value, runs)
// Output: 400 2

The previous value argument (Vue 3.4+)

The getter's parameter is the last value it returned, or undefined on the first run, which lets a computed ratchet in one direction or clamp a change. It was added in 3.4, so material written for earlier versions will not mention it; on an older Vue the parameter is simply undefined every time and the value would fall back to count.

Example · ts
// src/lib/high-water.ts
import { computed, ref } from 'vue'

const count = ref(0)

// Since Vue 3.4 the getter receives the previous computed value.
const highWater = computed<number>((previous) =>
  previous === undefined ? count.value : Math.max(previous, count.value)
)

console.log(highWater.value)
// Output: 0
count.value = 5
console.log(highWater.value)
// Output: 5
count.value = 2
console.log(highWater.value)
// Output: 5

Discussion

  • Be the first to comment on this lesson.