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 } // ✅
}