Geavanceerde Vue 3 Composables: Herbruikbare Patronen en Interviewvragen 2026
Een diepgaande gids over geavanceerde Vue 3 composable-patronen, van async data-ophaling tot formuliervalidatie en dependency injection. Inclusief veelgestelde interviewvragen over de Composition API in 2026.

Vue 3 composables zijn uitgegroeid tot het standaardmechanisme voor het extraheren en hergebruiken van reactieve logica in componenten. Nu Vue 3.6 Alien Signals en Vapor Mode-compatibiliteit introduceert, heeft het beheersen van composable-patronen in 2026 directe invloed op applicatiearchitectuur, prestaties en onderhoudbaarheid.
Een composable is een functie die gebruikmaakt van de Composition API om stateful logica in te kapselen en te hergebruiken. In tegenstelling tot mixins bieden composables expliciete invoer en uitvoer, volledige TypeScript-inferentie en geen naamgevingsconflicten. De conventie is om composable-namen te voorzien van het voorvoegsel use (bijvoorbeeld useCounter, useFetch).
Anatomie van een Goed Gestructureerde Composable
Een productiewaardige composable volgt een voorspelbare structuur: configuratie accepteren via argumenten, intern reactieve state aanmaken en een getypeerd return-object blootstellen. Dit patroon waarborgt composeerbaarheid, testbaarheid en duidelijke API-grenzen.
import { ref, computed, type Ref } from 'vue'
interface UseCounterOptions {
min?: number
max?: number
initialValue?: number
}
interface UseCounterReturn {
count: Ref<number>
doubled: Ref<number>
increment: () => void
decrement: () => void
reset: () => void
}
export function useCounter(options: UseCounterOptions = {}): UseCounterReturn {
const { min = 0, max = Infinity, initialValue = 0 } = options
const count = ref(initialValue)
const doubled = computed(() => count.value * 2)
function increment() {
if (count.value < max) count.value++
}
function decrement() {
if (count.value > min) count.value--
}
function reset() {
count.value = initialValue
}
return { count, doubled, increment, decrement, reset }
}Het expliciete return-type UseCounterReturn dient twee doelen: het documenteert de publieke API van de composable en het voorkomt onbedoelde blootstelling van interne implementatiedetails. Consumerende componenten destructureren precies wat ze nodig hebben, waardoor template-bindings transparant blijven.
Async Composables met Foutafhandeling en Laadstatussen
Het ophalen van data blijft een van de meest voorkomende toepassingen voor composables. Een robuuste async composable beheert laadstatus, foutafhandeling en automatische opschoning — patronen waar interviewers regelmatig naar vragen.
import { ref, watchEffect, onUnmounted, toValue, type Ref, type MaybeRefOrGetter } from 'vue'
interface UseFetchReturn<T> {
data: Ref<T | null>
error: Ref<string | null>
isLoading: Ref<boolean>
refresh: () => Promise<void>
}
export function useFetchData<T>(
url: MaybeRefOrGetter<string>
): UseFetchReturn<T> {
const data = ref<T | null>(null) as Ref<T | null>
const error = ref<string | null>(null)
const isLoading = ref(false)
let abortController: AbortController | null = null
async function fetchData() {
// Cancel any in-flight request
abortController?.abort()
abortController = new AbortController()
isLoading.value = true
error.value = null
try {
const response = await fetch(toValue(url), {
signal: abortController.signal
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
data.value = await response.json()
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') return
error.value = err instanceof Error ? err.message : 'Unknown error'
} finally {
isLoading.value = false
}
}
// Re-fetch when URL changes reactively
watchEffect(() => {
fetchData()
})
onUnmounted(() => abortController?.abort())
return { data, error, isLoading, refresh: fetchData }
}Verschillende details zijn hier van belang. Het parametertype MaybeRefOrGetter<string> accepteert gewone strings, refs of getters, wat maximale flexibiliteit biedt voor aanroepers. De AbortController voorkomt race conditions wanneer de URL snel verandert. Opschoning via onUnmounted vermijdt geheugenlekken en state-updates op vernietigde componenten.
Composables die onMounted, onUnmounted of andere lifecycle hooks aanroepen, moeten synchroon worden aangeroepen binnen setup(). Het aanroepen van een composable binnen een async callback of een setTimeout zal er stilzwijgend voor zorgen dat de lifecycle hook niet wordt geregistreerd, omdat er geen actieve component-instantie is.
Composable Compositie: Hogere Abstracties Bouwen
De werkelijke kracht van composables komt naar voren wanneer ze andere composables samenstellen. Dit weerspiegelt het principe van functionele compositie: kleine, gerichte eenheden worden gecombineerd tot complex gedrag zonder overerving.
import { ref, computed, watch, type Ref } from 'vue'
import { useFetchData } from './useFetchData'
import { useDebouncedRef } from './useDebouncedRef'
interface UsePaginatedSearchReturn<T> {
query: Ref<string>
page: Ref<number>
results: Ref<T[] | null>
totalPages: Ref<number>
isLoading: Ref<boolean>
error: Ref<string | null>
nextPage: () => void
prevPage: () => void
}
export function usePaginatedSearch<T>(
baseUrl: string,
perPage = 20
): UsePaginatedSearchReturn<T> {
const query = useDebouncedRef('', 300)
const page = ref(1)
const totalPages = ref(1)
const apiUrl = computed(
() => `${baseUrl}?q=${encodeURIComponent(query.value)}&page=${page.value}&limit=${perPage}`
)
const { data, error, isLoading } = useFetchData<{ items: T[]; total: number }>(apiUrl)
const results = computed(() => data.value?.items ?? null)
watch(data, (response) => {
if (response) {
totalPages.value = Math.ceil(response.total / perPage)
}
})
// Reset to page 1 when query changes
watch(query, () => { page.value = 1 })
function nextPage() {
if (page.value < totalPages.value) page.value++
}
function prevPage() {
if (page.value > 1) page.value--
}
return { query, page, results, totalPages, isLoading, error, nextPage, prevPage }
}Dit voorbeeld illustreert hoe usePaginatedSearch drie bestaande composables samenbrengt: useDebouncedRef voor vertraagde zoekinvoer, useFetchData voor het daadwerkelijk ophalen van data, en standaard Vue-reactiviteit voor paginabeheer. Het resultaat is een complete zoekoplossing met paginering die in elke component kan worden ingezet met een enkele functieaanroep. Deze aanpak elimineert codeduplicatie en maakt elke individuele composable onafhankelijk testbaar.
Klaar om je Vue.js / Nuxt.js gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
Dependency Injection met provide/inject voor Composable-Architectuur
Voor applicatiebrede state die gedeeld moet worden over de componentenboom zonder prop drilling, bieden provide en inject een elegant mechanisme. Het combineren van deze Vue-primitieven met composables levert een schone architectuur op die zowel typeveilig als goed gestructureerd is.
import { provide, inject, ref, readonly, type InjectionKey, type Ref } from 'vue'
type Theme = 'light' | 'dark' | 'system'
interface ThemeContext {
theme: Readonly<Ref<Theme>>
setTheme: (t: Theme) => void
resolvedTheme: Readonly<Ref<'light' | 'dark'>>
}
const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme')
export function provideTheme(initial: Theme = 'system') {
const theme = ref<Theme>(initial)
const resolvedTheme = computed<'light' | 'dark'>(() => {
if (theme.value !== 'system') return theme.value
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
})
function setTheme(t: Theme) {
theme.value = t
}
const context: ThemeContext = {
theme: readonly(theme),
setTheme,
resolvedTheme: readonly(resolvedTheme)
}
provide(ThemeKey, context)
return context
}
export function useTheme(): ThemeContext {
const context = inject(ThemeKey)
if (!context) {
throw new Error('useTheme() requires a parent component to call provideTheme()')
}
return context
}Het patroon van gescheiden provide- en inject-functies versterkt de scheiding van verantwoordelijkheden. De provideTheme-functie wordt aangeroepen in een bovenliggende component (doorgaans het root-component of een layout-component), terwijl useTheme overal in de componentenboom beschikbaar is. Het gebruik van readonly voor de blootgestelde refs voorkomt dat consumerende componenten de state rechtstreeks muteren, waardoor alle wijzigingen via de setTheme-methode verlopen. De InjectionKey met generiek type zorgt ervoor dat TypeScript het juiste type afleidt bij het aanroepen van inject.
Herbruikbare Formuliervalidatie-Composable
Formuliervalidatie is een ander domein waar composables aanzienlijke waarde toevoegen. In plaats van validatielogica te verspreiden over meerdere componenten, centraliseert een validatie-composable de regels en foutafhandeling in een herbruikbare eenheid.
import { reactive, computed, type UnwrapNestedRefs } from 'vue'
type ValidationRule<T> = (value: T) => string | true
type FieldRules<T> = { [K in keyof T]?: ValidationRule<T[K]>[] }
interface UseFormReturn<T extends Record<string, any>> {
fields: UnwrapNestedRefs<T>
errors: Record<keyof T, string>
isValid: Ref<boolean>
validate: () => boolean
resetErrors: () => void
}
export function useFormValidation<T extends Record<string, any>>(
initialValues: T,
rules: FieldRules<T>
): UseFormReturn<T> {
const fields = reactive({ ...initialValues }) as UnwrapNestedRefs<T>
const errors = reactive(
Object.keys(initialValues).reduce(
(acc, key) => ({ ...acc, [key]: '' }),
{} as Record<keyof T, string>
)
)
function validate(): boolean {
let valid = true
for (const key of Object.keys(rules) as (keyof T)[]) {
const fieldRules = rules[key] || []
errors[key] = '' as any
for (const rule of fieldRules) {
const result = rule(fields[key])
if (result !== true) {
errors[key] = result as any
valid = false
break // Stop at first error per field
}
}
}
return valid
}
function resetErrors() {
for (const key of Object.keys(errors)) {
(errors as any)[key] = ''
}
}
const isValid = computed(() =>
Object.values(errors).every((e) => e === '')
)
return { fields, errors, isValid, validate, resetErrors }
}De generieke typeparameter T stelt deze composable in staat om met elk formuliermodel te werken. De validatieregels worden gedefinieerd als een array van functies per veld, die ofwel true retourneren (geldig) of een foutmelding als string (ongeldig). Het break-statement na de eerste fout per veld voorkomt dat gebruikers worden overspoeld met meerdere foutmeldingen tegelijk. De isValid computed property biedt een reactieve samenvatting van de gehele formulierstatus, wat handig is voor het conditioneel activeren van verzendknoppen.
Composables Testen in Isolatie
Het testen van composables vereist een lichte wrapper, aangezien composables binnen een actieve Vue component-context moeten draaien. Het withSetup-hulppatroon biedt een minimale component die de composable aanroept en het resultaat blootstelt voor assertions.
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import { defineComponent, h } from 'vue'
import { useCounter } from './useCounter'
function withSetup<T>(composable: () => T): { result: T; unmount: () => void } {
let result!: T
const wrapper = mount(
defineComponent({
setup() {
result = composable()
return () => h('div')
}
})
)
return { result, unmount: () => wrapper.unmount() }
}
describe('useCounter', () => {
it('initializes with default value', () => {
const { result } = withSetup(() => useCounter())
expect(result.count.value).toBe(0)
})
it('respects min and max boundaries', () => {
const { result } = withSetup(() =>
useCounter({ min: 0, max: 3, initialValue: 3 })
)
result.increment()
expect(result.count.value).toBe(3) // Capped at max
result.count.value = 0
result.decrement()
expect(result.count.value).toBe(0) // Capped at min
})
it('computes doubled value reactively', () => {
const { result } = withSetup(() => useCounter({ initialValue: 5 }))
expect(result.doubled.value).toBe(10)
result.increment()
expect(result.doubled.value).toBe(12)
})
})Dit testpatroon is bijzonder waardevol omdat het de composable isoleert van component-specifieke zorgen. De withSetup-functie creert een wegwerp-component uitsluitend voor het bieden van de benodigde Vue-context. Na het aanroepen is result direct beschikbaar voor assertions. De unmount-functie maakt het mogelijk om opschoningsgedrag te testen, zoals het afbreken van lopende verzoeken of het verwijderen van event listeners.
VueUse bevat meer dan 200 productie-composables die browser-API's, sensoren, animaties en hulpprogramma's omvatten. Het bestuderen van de broncode onthult consistente patronen: options-objecten voor configuratie, SSR-veiligheidscontroles en tryOnScopeDispose voor opschoning. Het dient als een uitstekende referentie voor architectuurbeslissingen rondom composables.
Veelgestelde Interviewvragen over Vue 3 Composables
Deze sectie behandelt de meest voorkomende technische interviewvragen over Vue 3 composables. Elke vraag weerspiegelt scenario's die zich voordoen in professionele Vue-ontwikkeling en die aantonen of een kandidaat de Composition API op diepgaand niveau begrijpt.
Waarin verschillen composables van mixins, en waarom zijn mixins afgeraden?
Mixins kampen met drie structurele problemen: impliciet samenvoegen van properties leidt tot naamgevingsconflicten, de herkomst van data en methoden wordt ondoorzichtig in componenten die meerdere mixins gebruiken, en TypeScript kan geen types afleiden via mixin-compositie. Composables lossen alle drie de problemen op door expliciete objecten te retourneren, waardoor de dataflow traceerbaar wordt, en door volledige type-inferentie te bieden via standaard functiesignaturen.
Wanneer moet een composable shallowRef gebruiken in plaats van ref?
Gebruik shallowRef wanneer de ref een groot object of array bevat dat in zijn geheel wordt vervangen in plaats van gemuteerd. Aangezien shallowRef alleen toewijzing bijhoudt (geen diepe property-wijzigingen), vermijdt het de overhead van diepe reactieve proxying. Vue 3.6 gebruikt om deze prestatieredenen standaard ondiepe reactiviteit in bepaalde contexten.
Wat gebeurt er als een composable onMounted aanroept binnen een async functie?
De registratie van de lifecycle hook faalt stilzwijgend. Vue koppelt lifecycle hooks aan de momenteel actieve component-instantie, die wordt ingesteld tijdens de synchrone uitvoering van setup(). Tegen de tijd dat een async callback wordt uitgevoerd, kan de actieve instantie null zijn of een andere component betreffen. De oplossing: roep lifecycle hooks synchroon aan op het bovenste niveau van de composable, en gebruik watchEffect of watch voor asynchrone logica.
Hoe werken composables samen met Vue's Vapor Mode in 3.6?
Vapor Mode compileert templates naar directe DOM-operaties en omzeilt daarmee de virtuele DOM. Composables werken identiek in Vapor Mode omdat ze opereren op de reactiviteitslaag, niet op de renderinglaag. De reactieve refs en computed properties van composables activeren fijnmazige DOM-updates die efficienter verlopen onder Vapor Mode, waardoor goed gestructureerde composables een prestatievoordeel opleveren.
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Conclusie
Het beheersen van Vue 3 composables is in 2026 onmisbaar voor zowel het bouwen van schaalbare applicaties als het succesvol doorlopen van technische interviews. De belangrijkste inzichten uit dit artikel:
- Structuur is bepalend: een goed gestructureerde composable accepteert configuratie via een options-object, beheert interne reactieve state en retourneert een getypeerd interface. Dit patroon garandeert herbruikbaarheid en testbaarheid.
- Async composables vereisen discipline: het correct afhandelen van laadstatussen, fouten en het annuleren van lopende verzoeken via
AbortControlleronderscheidt productiewaardige code van prototypes. - Compositie boven overerving: de kracht van composables ligt in het samenstellen van kleinere eenheden tot complexe functionaliteit. Dit functionele patroon elimineert de broosheid van overervingshierarchieen.
- Dependency injection via provide/inject biedt een typeveilig alternatief voor global state management, vooral geschikt voor thema's, authenticatie en andere cross-cutting concerns.
- Testbaarheid is ingebouwd: met het
withSetup-patroon kunnen composables in volledige isolatie worden getest, los van de componenten die ze consumeren. - Vapor Mode-compatibiliteit: composables die correct zijn gestructureerd, profiteren automatisch van de prestatieverbeteringen van Vapor Mode, aangezien ze op de reactiviteitslaag opereren.
Het investeren in het doorgronden van deze patronen levert niet alleen betere applicaties op, maar bereidt ontwikkelaars ook voor op de diepgaande technische vragen die in moderne Vue-interviews aan bod komen.
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Tags
Delen
Gerelateerde artikelen

Nuxt 4 in 2026: Nieuwe Directorystructuur en Migratie vanuit Nuxt 3
Uitgebreide gids over Nuxt 4 in 2026: de nieuwe app/-directorystructuur, shallow reactivity, verbeterde TypeScript-integratie, Unhead v2 en een praktische migratiechecklist voor Nuxt 3-projecten.

Vue 3 Pinia vs Vuex: Modern State Management en Sollicitatievragen 2026
Pinia vs Vuex vergeleken: API-ontwerp, TypeScript-ondersteuning, performance, migratiestrategieën en veelgestelde Vue state management sollicitatievragen voor 2026.

Essentiële Vue.js-sollicitatievragen: 25 vragen om de baan te krijgen
Bereid Vue.js-sollicitatiegesprekken voor met deze 25 essentiële vragen. Van reactiviteit tot composables: beheers de kernconcepten voor het volgende gesprek.