Wat is een composable?

Een composable is gewoon een JavaScript-functie die reactive state en functies teruggeeft, zodat je ze kunt hergebruiken in meerdere componenten.

Drie regels:

  • De naam begint met use (conventie): useCounter, useFetch, useDarkMode
  • Komt in src/composables/
  • Eén composable per bestand, met dezelfde naam

Vue vs React: Composables zijn Vue's antwoord op React's custom hooks. Zelfde idee, andere naam.

Wanneer gebruik je een composable?

Als je dezelfde logica in meerdere componenten gebruikt:

  • Counter (count up/down/reset)
  • Window size bijhouden
  • localStorage syncen
  • Data ophalen met loading/error states
  • Een toast/notificatie systeem

Je eerste composable

Stel je gebruikt een teller in meerdere componenten. Zonder composable schrijf je elke keer hetzelfde:

<!-- Component A -->
<script setup>
import { ref } from 'vue'

const count = ref(0)
const increment = () => count.value++
const reset = () => count.value = 0
</script>

<!-- Component B -->
<script setup>
import { ref } from 'vue'

const count = ref(0)
const increment = () => count.value++
const reset = () => count.value = 0
</script>

Duplicatie. Trek het uit in een composable:

1. Maak het bestand

src/
└── composables/
    └── useCounter.js

2. Schrijf de composable

// src/composables/useCounter.js
import { ref } from 'vue'

export function useCounter(startValue = 0) {
  const count = ref(startValue)

  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = startValue

  return {
    count,
    increment,
    decrement,
    reset
  }
}

Wat is er bijzonder? Niets! Het is gewoon een functie die refs maakt en teruggeeft. Geen Vue-magic — maar door de use-naam herkennen andere developers het als composable.

Gebruiken in een component

In elk component dat de teller nodig heeft: importeren en aanroepen.

<script setup>
import { useCounter } from '@/composables/useCounter'

const { count, increment, decrement, reset } = useCounter(10)
</script>

<template>
  <p>Count: {{ count }}</p>
  <button @click="increment">+1</button>
  <button @click="decrement">−1</button>
  <button @click="reset">Reset</button>
</template>

Het mooie: elke component dat useCounter() aanroept krijgt een eigen teller. Ze delen geen state. Wil je wel gedeelde state? → gebruik Pinia.

Voorbeeld: useLocalStorage

Een veel-gebruikte composable: een ref die automatisch synct met localStorage.

// src/composables/useLocalStorage.js
import { ref, watch } from 'vue'

export function useLocalStorage(key, defaultValue) {
  // Lees uit localStorage, of gebruik default
  const stored = localStorage.getItem(key)
  const initial = stored !== null ? JSON.parse(stored) : defaultValue

  const value = ref(initial)

  // Sync bij elke wijziging
  watch(value, (newVal) => {
    localStorage.setItem(key, JSON.stringify(newVal))
  }, { deep: true })

  return value
}

Gebruik

<script setup>
import { useLocalStorage } from '@/composables/useLocalStorage'

// Ref die automatisch saved in localStorage
const username = useLocalStorage('username', '')
const todos = useLocalStorage('todos', [])
</script>

<template>
  <input v-model="username" />
  <!-- Bij elke wijziging: opgeslagen in localStorage -->
  <!-- Bij refresh: waarde wordt teruggehaald -->
</template>

Eén keer schrijven, overal gebruiken. In elke component die een waarde wil bewaren tussen sessies: één regel code.

VueUse — kant-en-klare composables

Voordat je een composable zelf bouwt: check VueUse. Dat is een library met 200+ kant-en-klare composables voor de meeste dingen die je nodig hebt.

npm install @vueuse/core
<script setup>
import { useLocalStorage, useWindowSize, useDark } from '@vueuse/core'

const username = useLocalStorage('username', '')
const { width, height } = useWindowSize()
const isDark = useDark()
</script>

Aanbeveling: Bouw composables zelf voor logica die specifiek is voor jouw app. Voor algemene dingen (localStorage, debounce, mouse position, dark mode) → gebruik VueUse.

Belangrijke Regels

  • Naam begint met use (conventie)
  • Eén composable per bestand in src/composables/
  • Geeft refs terug — en eventueel functies om ze te muteren
  • Roep aan in <script setup>, niet binnen een conditie of loop
  • Elke aanroep is onafhankelijk — wil je gedeelde state? → Pinia

Veelgemaakte Fouten

Fout — composable buiten <script setup> aanroepen:

// In een gewone JS-functie
function regularFunction() {
  const { count } = useCounter()   // ❌ werkt niet altijd
}

Goed — alleen in een component, op top-level:

<script setup>
const { count } = useCounter()    // ✅
</script>

Fout — vergeten te return:

export function useCounter() {
  const count = ref(0)
  const increment = () => count.value++
  // ❌ niks ge-returned
}

Goed:

export function useCounter() {
  const count = ref(0)
  const increment = () => count.value++

  return { count, increment }    // ✅
}