Vue 3 tutorial: computed properties and how caching works
verified on 2 September 2026 · 4 min
computed() declares a value derived from other values. Vue caches the result and only recalculates it when a dependency changes, unlike a function called from the template, which runs on every render. A computed property has to be pure: it reads and returns, with no side effect.
A computed property is a value derived from other values. Vue recalculates it when one of its dependencies changes, and only then. It is the tool that keeps logic out of the template.
A first computation
The product page has to show the brand and the product. You could do it in the template:
<h1>{{ brand + ' ' + product }}</h1>It works, but the logic scatters through the HTML. Declare a computed property instead:
<script setup>
import { computed, ref } from 'vue'
const product = ref('T-Shirt')
const brand = ref('Gekkode')
const title = computed(() => `${brand.value} ${product.value}`)
</script>
<template>
<h1>{{ title }}</h1>
</template>computed takes a function and returns a read-only ref. In the script you read title.value, in the template, {{ title }}, just like a ref.
Caching
This is the real difference with an ordinary function. A computed property holds on to its result and only recalculates it when one of its dependencies has changed.
<!-- appelée à chaque rendu, même si rien n’a changé -->
<p>{{ calculerTitre() }}</p>
<!-- calculée une fois, puis relue depuis le cache -->
<p>{{ title }}</p>On a concatenation, the difference is nil. On a sort or a filter applied to several hundred entries, and shown in three places on the page, it becomes obvious.
Driving the selected variant
On to a useful case. Each variant now has its own stock quantity:
<script setup>
const variants = ref([
{ id: 2234, color: '#2563eb', image: '/images/t-shirt-bleu.svg', quantity: 20 },
{ id: 2235, color: '#dc2626', image: '/images/t-shirt-rouge.svg', quantity: 0 }
])
</script>The blue one is available, the red one is not. The image shown and the availability now both depend on the variant being hovered. Rather than keeping two variables up to date by hand, let us hold on to a single piece of information: which one is selected.
<script setup>
const selectedVariant = ref(0)
function updateVariant(index) {
selectedVariant.value = index
}
</script>The loop passes on the index, picked up as in chapter 4:
<div
v-for="(variant, index) in variants"
:key="variant.id"
class="color-circle"
:style="{ backgroundColor: variant.color }"
@mouseover="updateVariant(index)"
></div>The rest follows from it. image and inStock are no longer data but computations:
<script setup>
const image = computed(() => variants.value[selectedVariant.value].image)
const inStock = computed(() => variants.value[selectedVariant.value].quantity > 0)
</script>Hover over the red swatch: the image changes, the message switches to “En rupture”, and the button greys out thanks to the class binding. One variable moved, the three displays followed.
Writing computed(() => variants.value[selectedVariant.value].quantity) looks like it works: a quantity of 0 is falsy, a quantity of 20 is truthy. But inStock then holds 20, and the day you display it with {{ inStock }}, the page will show “20”. Compare explicitly with > 0.
When to use a computed property
- For a value derived from state: a basket total, a filtered list, an assembled string. That is the normal case.
- Not for a side effect. A computed property has to be a pure function: it reads, it returns. It changes nothing, calls no API, fires no request. That is what
watchandwatchEffectare for. - Not for a value that changes on its own, such as the current time: nothing would tell Vue to recalculate.
A writable computed property
By default a computed property is read-only. Vue warns in the console if you try to assign to it. For the cases where writing makes sense, provide a get / set pair:
const nomComplet = computed({
get: () => `${prenom.value} ${nom.value}`,
set: (valeur) => {
[prenom.value, nom.value] = valeur.split(' ')
}
})Computed properties live in the computed option and read state through this: computed: { title() { return this.brand + ' ' + this.product } }. The caching principle is the same.
resume that returns “Gekkode T-Shirt, blue, in stock” from the selected variant. Then check with a console.log inside the function that it is not recalculated on every render.Common errors
quantity > 0.watch or watchEffect.