# 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. - Published: 2026-05-12 - Updated: 2026-05-12 - Author: SharpSkill - Tags: vue, composables, typescript, interview - Reading time: 12 min --- 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. > **Wat is een Vue 3 Composable?** > > 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. ```typescript // useCounter.ts import { ref, computed, type Ref } from 'vue' interface UseCounterOptions { min?: number max?: number initialValue?: number } interface UseCounterReturn { count: Ref doubled: Ref 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. ```typescript // useFetchData.ts import { ref, watchEffect, onUnmounted, toValue, type Ref, type MaybeRefOrGetter } from 'vue' interface UseFetchReturn { data: Ref error: Ref isLoading: Ref refresh: () => Promise } export function useFetchData( url: MaybeRefOrGetter ): UseFetchReturn { const data = ref(null) as Ref const error = ref(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` 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. > **Lifecycle Hooks Binnen Composables** > > 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. ```typescript // usePaginatedSearch.ts import { ref, computed, watch, type Ref } from 'vue' import { useFetchData } from './useFetchData' import { useDebouncedRef } from './useDebouncedRef' interface UsePaginatedSearchReturn { query: Ref page: Ref results: Ref totalPages: Ref isLoading: Ref error: Ref nextPage: () => void prevPage: () => void } export function usePaginatedSearch( baseUrl: string, perPage = 20 ): UsePaginatedSearchReturn { 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. ## 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. ```typescript // useTheme.ts import { provide, inject, ref, readonly, type InjectionKey, type Ref } from 'vue' type Theme = 'light' | 'dark' | 'system' interface ThemeContext { theme: Readonly> setTheme: (t: Theme) => void resolvedTheme: Readonly> } const ThemeKey: InjectionKey = Symbol('theme') export function provideTheme(initial: Theme = 'system') { const theme = ref(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. ```typescript // useFormValidation.ts import { reactive, computed, type UnwrapNestedRefs } from 'vue' type ValidationRule = (value: T) => string | true type FieldRules = { [K in keyof T]?: ValidationRule[] } interface UseFormReturn> { fields: UnwrapNestedRefs errors: Record isValid: Ref validate: () => boolean resetErrors: () => void } export function useFormValidation>( initialValues: T, rules: FieldRules ): UseFormReturn { const fields = reactive({ ...initialValues }) as UnwrapNestedRefs const errors = reactive( Object.keys(initialValues).reduce( (acc, key) => ({ ...acc, [key]: '' }), {} as Record ) ) 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. ```typescript // useCounter.spec.ts import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import { defineComponent, h } from 'vue' import { useCounter } from './useCounter' function withSetup(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 als Referentiearchitectuur** > > 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. ## 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 `AbortController` onderscheidt 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. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/nl/blog/vue-nuxt/advanced-vue-3-composables-reusable-patterns