Computed Properties
What are Computed Properties?
Computed properties are reactive, cached values derived from other reactive data. They automatically re-evaluate only when their dependencies change — unlike methods, which re-run on every render.
| Feature | Computed | Method | Watch |
|---|---|---|---|
| Cached | Yes — only recalculates when deps change | No — runs every render | N/A |
| Returns value | Yes | Yes | No (side effects) |
| Async support | No | Yes | Yes |
| Best for | Derived data, filtering, formatting | Event handlers, actions | Side effects, async ops |
<template>
<div>
<p>Full name: {{ fullName }}</p>
<p>Reversed: {{ reversedMessage }}</p>
<p>Active users: {{ activeCount }}</p>
<!-- Writable computed -->
<input v-model="fullNameWritable" placeholder="First Last" />
<p>First: {{ firstName }}, Last: {{ lastName }}</p>
<!-- Computed vs method — same result, different performance -->
<p>Computed (cached): {{ expensiveComputed }}</p>
<p>Method (not cached): {{ expensiveMethod() }}</p>
</div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
const firstName = ref('Alice')
const lastName = ref('Smith')
const message = ref('Hello Vue!')
const users = reactive([
{ id: 1, name: 'Alice', active: true },
{ id: 2, name: 'Bob', active: false },
{ id: 3, name: 'Carol', active: true },
])
// Read-only computed
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
const reversedMessage = computed(() => message.value.split('').reverse().join(''))
const activeCount = computed(() => users.filter(u => u.active).length)
// Writable computed — get + set
const fullNameWritable = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (val) => {
const parts = val.trim().split(' ')
firstName.value = parts[0] || ''
lastName.value = parts.slice(1).join(' ') || ''
}
})
// Expensive computation — computed caches the result
const expensiveComputed = computed(() => {
console.log('Computing...') // only runs when users changes
return users.reduce((sum, u) => sum + u.name.length, 0)
})
// Method — runs on every render
function expensiveMethod() {
console.log('Method called...') // runs every time template re-renders
return users.reduce((sum, u) => sum + u.name.length, 0)
}
</script>
<template>
<div class="cart">
<div v-for="item in cart" :key="item.id" class="cart-item">
<span>{{ item.name }}</span>
<input type="number" v-model.number="item.qty" min="1" />
<span>${{ (item.price * item.qty).toFixed(2) }}</span>
<button @click="removeItem(item.id)">Remove</button>
</div>
<div class="cart-summary">
<p>Items: {{ totalItems }}</p>
<p>Subtotal: ${{ subtotal }}</p>
<p v-if="hasDiscount">Discount (10%): -${{ discount }}</p>
<p><strong>Total: ${{ total }}</strong></p>
<p v-if="freeShipping" class="success">Free shipping!</p>
<p v-else>Add ${{ shippingThreshold - subtotal }} more for free shipping</p>
</div>
</div>
</template>
<script setup>
import { reactive, computed } from 'vue'
const cart = reactive([
{ id: 1, name: 'Vue T-Shirt', price: 25.00, qty: 2 },
{ id: 2, name: 'Vue Mug', price: 15.00, qty: 1 },
{ id: 3, name: 'Vue Sticker', price: 5.00, qty: 3 },
])
const shippingThreshold = 50
const totalItems = computed(() => cart.reduce((sum, i) => sum + i.qty, 0))
const subtotal = computed(() => cart.reduce((sum, i) => sum + i.price * i.qty, 0).toFixed(2))
const hasDiscount = computed(() => Number(subtotal.value) >= 100)
const discount = computed(() => (Number(subtotal.value) * 0.1).toFixed(2))
const total = computed(() => (Number(subtotal.value) - (hasDiscount.value ? Number(discount.value) : 0)).toFixed(2))
const freeShipping = computed(() => Number(subtotal.value) >= shippingThreshold)
function removeItem(id) {
const idx = cart.findIndex(i => i.id === id)
if (idx !== -1) cart.splice(idx, 1)
}
</script>
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.