Wat is een Computed Property?

Een computed property is een waarde die automatisch berekend wordt op basis van andere reactive data. Als de bron-data verandert, wordt de computed automatisch geüpdatet.

Denk aan een Excel-cel met een formule:

Cel A1 = 5, cel A2 = 10, cel A3 = =A1+A2. Wijzig A1 en A3 update zichzelf. Computed properties werken precies zo.

Wanneer gebruik je computed?

  • Een volledige naam uit voor- en achternaam berekenen
  • Een totaalbedrag uit een winkelmandje
  • Een gefilterde of gesorteerde versie van een array
  • Iets booleaans: isAdult = age >= 18
  • Een format van data: formattedDate

Vue's superkracht: Computed properties zijn een van de mooiste features van Vue. In React zou je hiervoor useMemo gebruiken — en die heeft alle problemen die computed niet heeft (dependency-array, stale closures, etc.).

Basis Syntax

Importeer computed uit Vue:

import { computed } from 'vue'

Voorbeeld: Volledige naam

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

const firstName = ref('Anna')
const lastName = ref('Jansen')

const fullName = computed(() => {
  return firstName.value + ' ' + lastName.value
})
</script>

<template>
  <input v-model="firstName" />
  <input v-model="lastName" />
  <p>Volledige naam: {{ fullName }}</p>
</template>

Typ in een van de inputs en zie hoe fullName automatisch meeloopt.

Belangrijk:

  • Een computed is read-only standaard (geen fullName.value = ...)
  • In de template gebruik je {{ fullName }} — zonder .value
  • In JS lees je fullName.value — net als een gewone ref
  • Vue detecteert automatisch welke refs gebruikt zijn in de computed — je hoeft geen dependency-array bij te houden

De Cache Magic

Het belangrijkste verschil tussen een computed en een gewone functie: een computed cached zijn resultaat.

Wat betekent "cached"?

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

const items = ref([1, 2, 3, 4, 5])

const total = computed(() => {
  console.log('total opnieuw berekend!')
  return items.value.reduce((sum, n) => sum + n, 0)
})
</script>

<template>
  <p>Totaal: {{ total }}</p>
  <p>Nogmaals: {{ total }}</p>
  <p>En nog eens: {{ total }}</p>
</template>

In de console zie je:

total opnieuw berekend!

Eén keer! Niet drie keer. De computed is één keer berekend en het resultaat wordt drie keer hergebruikt.

Wanneer wordt opnieuw berekend?

Alleen als een van de gebruikte refs (in dit geval items) verandert. Pas na een wijziging draait de computed opnieuw.

items.value.push(6)
// Nu draait de computed wel opnieuw → 'total opnieuw berekend!'

Waarom is dit belangrijk? Stel je computed is duur (filteren van 10.000 items). Met een gewone functie zou die berekening bij elke render opnieuw draaien. Met een computed maar één keer — en daarna pas weer als er echt iets verandert.

Computed vs Methode

Je zou hetzelfde resultaat kunnen halen met een functie. Wat is het verschil?

Methode (gewone functie)

<script setup>
import { ref } from 'vue'

const items = ref([1, 2, 3])

const calculateTotal = () => {
  console.log('berekend')
  return items.value.reduce((s, n) => s + n, 0)
}
</script>

<template>
  <p>{{ calculateTotal() }}</p>
  <p>{{ calculateTotal() }}</p>
  <!-- Console: 'berekend' × 2 (bij elke render opnieuw) -->
</template>

Computed

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

const items = ref([1, 2, 3])

const total = computed(() => {
  console.log('berekend')
  return items.value.reduce((s, n) => s + n, 0)
})
</script>

<template>
  <p>{{ total }}</p>
  <p>{{ total }}</p>
  <!-- Console: 'berekend' × 1 (cached!) -->
</template>
Methode Computed
Aanroepen {{ fn() }} {{ value }}
Cache Nee — draait elke keer Ja — alleen bij wijziging
Argumenten Mogelijk: fn(x) Niet mogelijk
Gebruik voor Acties (klik, submit) Afgeleide waardes

Vuistregel: Bereken je een waarde uit andere reactive data? → computed. Doe je iets (klik, opslaan, fetch)? → methode.

Praktische Voorbeelden

1. Winkelmandje totaal

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

const cart = ref([
  { name: 'Boek', price: 15.99, qty: 2 },
  { name: 'Pen', price: 2.50, qty: 5 }
])

const totalPrice = computed(() => {
  return cart.value.reduce((sum, item) => {
    return sum + item.price * item.qty
  }, 0)
})

const totalItems = computed(() => {
  return cart.value.reduce((sum, item) => sum + item.qty, 0)
})
</script>

<template>
  <p>Aantal items: {{ totalItems }}</p>
  <p>Totaal: € {{ totalPrice.toFixed(2) }}</p>
</template>

2. Gefilterde lijst

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

const search = ref('')
const users = ref([
  { id: 1, name: 'Anna' },
  { id: 2, name: 'Bram' },
  { id: 3, name: 'Chloé' }
])

const filteredUsers = computed(() => {
  return users.value.filter(u =>
    u.name.toLowerCase().includes(search.value.toLowerCase())
  )
})
</script>

<template>
  <input v-model="search" placeholder="Zoek..." />
  <ul>
    <li v-for="user in filteredUsers" :key="user.id">
      {{ user.name }}
    </li>
  </ul>
</template>

3. Boolean checks

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

const age = ref(16)
const password = ref('')

const isAdult = computed(() => age.value >= 18)
const isStrongPassword = computed(() => password.value.length >= 8)
const canRegister = computed(() => isAdult.value && isStrongPassword.value)
</script>

<template>
  <button :disabled="!canRegister">Registreer</button>
</template>

Let op hoe canRegister twee andere computeds gebruikt. Je kunt ze stapelen.

Writable Computed (gevorderd)

Standaard is een computed read-only. Maar je kunt ook een setter definiëren — zo kun je v-model erop gebruiken.

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

const firstName = ref('Anna')
const lastName = ref('Jansen')

const fullName = computed({
  // getter — lezen
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter — schrijven
  set(newValue) {
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

<template>
  <input v-model="fullName" />
  <p>Voornaam: {{ firstName }}</p>
  <p>Achternaam: {{ lastName }}</p>
</template>

Wees voorzichtig: Writable computeds zijn handig in heel specifieke gevallen, maar maken je code complexer. Vraag jezelf eerst af of een gewone ref niet beter past.

Belangrijke Regels

  • Computed is voor afgeleide waardes, niet voor side effects (gebruik watch daarvoor)
  • Geen side effects in je computed (geen fetch, geen DOM-manipulatie, geen state muteren)
  • Computeds moeten "pure" zijn — gegeven dezelfde input, altijd dezelfde output
  • Gebruik computed i.p.v. een methode als je de waarde meerdere keren in je template gebruikt

Veelgemaakte Fouten

Fout — side effect in computed:

const data = computed(() => {
  fetch('/api')                  // ❌ side effect!
  return processed
})

Goed — gebruik watch of onMounted voor side effects:

// computed alleen voor de berekening
const filtered = computed(() => data.value.filter(...))

Fout — computed muteert state:

const result = computed(() => {
  count.value++                  // ❌ Niet doen!
  return count.value * 2
})

Goed — alleen lezen:

const result = computed(() => count.value * 2)

Fout — .value vergeten:

const total = computed(() => {
  return items.reduce(...)         // ❌ items is een ref, geen array
})

Goed:

const total = computed(() => {
  return items.value.reduce(...)   // ✅
})