# Zaawansowane Vue 3 Composables: Wzorce wielokrotnego uzytku i pytania rekrutacyjne 2026 > Kompleksowy przewodnik po zaawansowanych Vue 3 Composables z wzorcami wielokrotnego uzytku, asynchroniczna obsluga bledow, Dependency Injection, walidacja formularzy i aktualne pytania rekrutacyjne na 2026 rok. - Published: 2026-05-12 - Updated: 2026-05-12 - Author: SharpSkill - Tags: vue, vue-composables, composition-api, interview - Reading time: 12 min --- Vue 3 Composables ugruntowaly swoja pozycje jako podstawowy mechanizm ekstrakcji i ponownego wykorzystania reaktywnej logiki pomiedzy komponentami. Wraz z wprowadzeniem Alien Signals i kompatybilnoscia z Vapor Mode w Vue 3.6, bieglosc w zakresie wzorcow Composable bezposrednio wplywa na architekture aplikacji, wydajnosc i utrzymywalnosc kodu w 2026 roku. Composition API fundamentalnie zmienilo sposob, w jaki programisci organizuja i ponownie wykorzystuja logike w aplikacjach Vue. W centrum tej transformacji znajduja sie Composables: funkcje, ktore enkapsuluja reaktywny stan, logike biznesowa i efekty uboczne w niezalezne, wielokrotnie uzywane jednostki. W przeciwienstwie do mixinow z Vue 2, ktore powodowaly konflikty nazw i ukryte zaleznosci, Composables oferuja pelna przejrzystosc, statyczne typowanie dzieki TypeScript oraz jawna kompozycje. Opanowanie zaawansowanych wzorcow Composable stalo sie w 2026 roku fundamentalnym wymaganiem na stanowiskach mid i senior w ekosystemie Vue. Niniejszy przewodnik analizuje wzorce najczesciej pojawiajace sie w technicznych rozmowach kwalifikacyjnych -- od anatomii dobrze zbudowanego Composable, przez Dependency Injection z uzyciem `provide/inject`, az po strategie testowania reaktywnosci kazdego elementu. > **Czym jest Vue 3 Composable?** > > Composable to funkcja wykorzystujaca Composition API Vue do enkapsulacji i ponownego uzycia logiki zawierajacej stan. W przeciwienstwie do mixinow, Composables oferuja jawne wejscia i wyjscia, pelna inferencje typow TypeScript i brak jakichkolwiek kolizji nazw. Konwencja nakazuje prefiksowanie nazw Composable slowem `use` (np. `useCounter`, `useFetch`). ## Anatomia dobrze zbudowanego Composable Produkcyjny Composable podaza za przewidywalna struktura: przyjmuje konfiguracje przez argumenty, tworzy reaktywny stan wewnetrznie i udostepnia typowany obiekt zwrotny. Ten wzorzec gwarantuje kompozywalnosc, testowalnosc i czytelne granice API. ```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 } } ``` Kilka aspektow tego wzorca zasluguje na szczegolna uwage. Interfejs `UseCounterReturn` jawnie definiuje kontrakt Composable, co ulatwia autouzupelnianie w edytorach kodu i zapobiega przypadkowym zmianom w publicznym API. Obiekt opcji z wartosciami domyslnymi umozliwia elastyczna konfiguracje bez przeladowywania sygnatury funkcji. Kazde wywolanie `useCounter()` tworzy niezalezna instancje stanu, co calkowicie eliminuje problemy ze wspoldzielonym stanem znane z mixinow. Jawny typ zwrotny `UseCounterReturn` pelni podwojna role: dokumentuje publiczne API Composable i uniemozliwia przypadkowe ujawnienie wewnetrznych szczegolow implementacji. Komponenty konsumujace destrukturyzuja dokladnie to, czego potrzebuja, utrzymujac przejrzystosc wiazanw szablonie. ## Asynchroniczne Composables z obsluga bledow i stanami ladowania Pobieranie danych to jeden z najczestszych przypadkow uzycia Composables. Solidny asynchroniczny Composable zarzadza stanem ladowania, obsluga bledow i automatycznym czyszczeniem zasobow. Te wzorce sa regularnie sprawdzane podczas rozmow kwalifikacyjnych. ```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 } } ``` Ten Composable wprowadza kilka zaawansowanych koncepcji. Typ parametru `MaybeRefOrGetter` akceptuje zarowno zwykle stringi, jak i Refy czy funkcje getter, oferujac maksymalna elastycznosc dla wywolujacego. Funkcja `toValue()` ekstrahuje wartosc bazowa niezaleznie od typu wejscia. Wykorzystanie `AbortController` zapobiega wyscigom (race conditions), anulujac przestarzale zadania w momencie zmiany URL, zanim poprzednia odpowiedz zdazy nadejsc. `watchEffect` ustanawia automatyczne reaktywne sledzenie: kazda Ref lub getter, do ktorych uzyskiwany jest dostep wewnatrz callbacka, automatycznie staje sie zaleznoscia. Gdy URL jest Refem i jego wartosc sie zmieni, efekt zostanie automatycznie ponownie wykonany. Hook `onUnmounted` gwarantuje czyszczenie zasobow po zniszczeniu komponentu, zapobiegajac wyciekom pamieci. > **Hooki cyklu zycia wewnatrz Composables** > > Composables wywolujace `onMounted`, `onUnmounted` lub inne hooki cyklu zycia musza byc wywolywane synchronicznie wewnatrz `setup()`. Wywolanie Composable wewnatrz asynchronicznego callbacka lub `setTimeout` spowoduje, ze hook cyklu zycia po cichu nie zostanie zarejestrowany, poniewaz nie istnieje aktywna instancja komponentu. ## Kompozycja Composables: abstrakcje wyzszego poziomu Prawdziwa sila Composables ujawnia sie, gdy komponuja one inne Composables. Odzwierciedla to zasade kompozycji funkcyjnej: male, skupione na jednym zadaniu jednostki lacza sie w zlozone zachowanie bez koniecznosci korzystania z hierarchii dziedziczenia. ```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 } } ``` Kompozycja odbywa sie na wielu poziomach. `useDebouncedRef` dostarcza Ref z wbudowanym debouncingiem, ktory zapobiega nadmiernym zadaniom podczas wpisywania tekstu. `useFetchData` otrzymuje `apiUrl` jako `computed`, co oznacza, ze kazda zmiana `query` lub `page` przelicza URL i automatycznie wyzwala nowe zadanie. Watcher na `query` resetuje paginacje do strony 1 przy kazdej zmianie frazy wyszukiwania. Ten wzorzec demonstruje fundamentalna zasade: kazdy Composable rozwiazuje konkretny problem, a kompozycja laczy je bez tworzenia sprzezen. Podczas technicznej rozmowy kwalifikacyjnej wyjasnienie tego lancucha reaktywnosci (`query` sie zmienia -> `apiUrl` jest przeliczany -> `useFetchData` wykonuje ponowne zapytanie -> `results` sie aktualizuje) swiadczy o glebokim zrozumieniu systemu reaktywnosci Vue. ## Dependency Injection z provide/inject w architekturze Composables Niektore stany musza byc wspoldzielone w obrebie drzewa komponentow bez koniecznosci przekazywania propsow przez kazdy poziom hierarchii. System `provide/inject` Vue w polaczeniu z Composables tworzy typowany i bezpieczny wzorzec kontekstu. ```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 } ``` Ten wzorzec oddziela dostarczanie kontekstu od jego konsumpcji. Komponent glowny (lub layout) wywoluje `provideTheme()` jednokrotnie, a kazdy komponent potomny moze uzyskac dostep do kontekstu przez `useTheme()`, niezaleznie od glebokosci w drzewie komponentow. Uzycie `InjectionKey` z `Symbol` gwarantuje bezpieczenstwo typow w czasie kompilacji. Funkcja `readonly()` opakowuje eksponowane Refy, aby zapobiec bezposrednim mutacjom przez komponenty konsumujace. Jedynie `setTheme()` moze modyfikowac motyw, wymuszajac jednokierunkowy przeplyw danych. Wlasciwosc `resolvedTheme` rozwiazuje wartosc `'system'` przez odpytanie media query przegladarki, dostarczajac zawsze konkretna wartosc (`'light'` lub `'dark'`), ktora komponenty moga bezposrednio uzyc do zastosowania stylow. Podczas rozmow rekrutacyjnych ten wzorzec jest czesto porownywany z React Context lub storami Pinia. Kluczowa roznica polega na tym, ze `provide/inject` dziala na poziomie drzewa komponentow (nie globalnie) i nie wymaga zewnetrznych zaleznosci. Daje to precyzyjna kontrole nad zakresem wspoldzielonego stanu. ## Composable do walidacji formularzy Walidacja formularzy to obszar, w ktorym Composables szczegolnie sie sprawdzaja. Zastepuja powtarzalna logike deklaratywnym, opartym na regulach wzorcem, ktory mozna ponownie wykorzystac w calej aplikacji. ```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 } } ``` Composable przyjmuje wartosci poczatkowe oraz mape regul walidacji dla kazdego pola. Kazda regula to funkcja zwracajaca `true` w przypadku poprawnej wartosci lub string z komunikatem bledu. Metoda `validate()` iteruje po regulach i zatrzymuje sie na pierwszym bledzie kazdego pola, aby uniknac wyswietlania wielu komunikatow jednoczesnie, co mogloby dezorientowac uzytkownikow. Uzycie `reactive()` zamiast `ref()` dla pol i bledow upraszcza dostep w szablonach: `fields.email` zamiast `fields.value.email`. Obliczana wlasciwosc `isValid` aktualizuje sie automatycznie przy kazdej zmianie bledu, umozliwiajac reaktywne wlaczanie lub wylaczanie przyciskow wysylania formularza. Ten wzorzec jest latwo rozszerzalny. Asynchroniczne reguly (sprawdzanie dostepnosci adresu e-mail), walidacja miedzypolowa (potwierdzenie hasla) oraz zinternacjonalizowane komunikaty bledow moga byc dodane bez modyfikacji podstawowej struktury Composable. W praktyce produkcyjnej czesto laczy sie ten wzorzec z biblioteka typu Zod do definicji schematow walidacji. ## Testowanie Composables w izolacji Composables wykorzystujace reaktywne API Vue wymagaja kontekstu komponentu do prawidlowego dzialania. Funkcja pomocnicza `withSetup` rozwiazuje to wymaganie, tworzac minimalna komponent, ktory wykonuje Composable wewnatrz `setup()`. ```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) }) }) ``` Funkcja `withSetup` jest standardowym wzorcem w ekosystemie Vue do testowania Composables w izolacji. Montuje komponent opakowujacy, ktory wywoluje Composable wewnatrz `setup()`, i zapewnia dostep do wyniku oraz funkcji `unmount()` do symulacji zniszczenia komponentu. Kazdy test waliduje konkretny aspekt: inicjalizacje z wartosciami domyslnymi, przestrzeganie skonfigurowanych granic oraz reaktywnosc wartosci obliczanych. Trzeci test jest szczegolnie istotny, poniewaz weryfikuje, ze `doubled` automatycznie sie aktualizuje po zmianie `count` przez `increment()`, potwierdzajac prawidlowe dzialanie grafu reaktywnosci. W przypadku asynchronicznych Composables, takich jak `useFetchData`, testy wymagaja mockowania `fetch` oraz obslugi promisow za pomoca `flushPromises()` z Vue Test Utils. Podczas rozmow kwalifikacyjnych znajomosc tego wzorca testowania swiadczy o profesjonalnej dojrzalosci i dbaniu o jakosc kodu. > **VueUse jako architektura referencyjna** > > VueUse zawiera ponad 200 produkcyjnych Composables obejmujacych API przegladarki, sensory, animacje i funkcje pomocnicze. Analiza kodu zrodlowego ujawnia spojne wzorce: obiekty opcji do konfiguracji, zabezpieczenia SSR i `tryOnScopeDispose` do czyszczenia zasobow. Biblioteka stanowi doskonale zrodlo referencyjne przy podejmowaniu decyzji architektonicznych dotyczacych Composables. ## Najczestsze pytania rekrutacyjne dotyczace Vue 3 Composables Ponizsze pytania regularnie pojawiaja sie w procesach rekrutacyjnych na stanowiska mid i senior Vue w 2026 roku: **1. Jaka jest roznica miedzy Composable a mixinem?** Mixiny wstrzykuja wlasciwosci do komponentu w sposob niejawny, co prowadzi do konfliktow nazw i utrudnia sledzenie pochodzenia kazdej wlasciwosci. Composables zwracaja wartosci jawnie, oferuja pelne typowanie TypeScript i umozliwiaja zmiane nazw zmiennych podczas destrukturyzacji zwracanego obiektu. Kazde wywolanie tworzy niezalezna instancje stanu, podczas gdy mixiny wspoldziela stan miedzy wszystkimi komponentami, ktore je wlaczaja. **2. Dlaczego Composables musza byc wywolywane wewnatrz setup()?** Vue kojarzy hooki cyklu zycia (`onMounted`, `onUnmounted`, `watch`, `watchEffect`) z aktywna instancja komponentu podczas wykonywania `setup()`. Wywolanie Composable poza tym kontekstem przerywa te asocjacje, wskutek czego efekty nie zostana zarejestrowane ani wyczyszczone. Jest to jedno z najczesciej zadawanych pytan w technicznych rozmowach kwalifikacyjnych dotyczacych Vue. **3. Jak wspoldzielic stan miedzy wieloma komponentami za pomoca Composables?** Istnieja dwie podstawowe strategie. Dla stanu globalnego Ref deklaruje sie poza funkcja Composable (wzorzec Singleton). Dla stanu kontekstowego w poddrzewie komponentow stosuje sie `provide/inject` z typowanym `InjectionKey`, jak zademonstrowano w przykladzie `useTheme`. W przypadku bardziej zlozonego globalnego zarzadzania stanem rekomendowane jest uzycie Pinia. **4. Czym jest `MaybeRefOrGetter` i dlaczego jest wazny?** Jest to typ narzeddziowy Vue, ktory akceptuje zwykla wartosc, `Ref` lub funkcje getter. Umozliwia Composables przyjmowanie zarowno statycznych, jak i reaktywnych danych wejsciowych, maksymalizujac elastycznosc dla wywolujacego. Funkcja `toValue()` ekstrahuje wartosc bazowa niezaleznie od typu wejscia. **5. Jak testowac Composables korzystajace z onMounted lub onUnmounted?** Stosuje sie funkcje pomocnicza typu `withSetup`, ktora montuje komponent opakowujacy. Ten komponent wykonuje Composable wewnatrz `setup()`, zapewniajac wymagany kontekst instancji. W celu weryfikacji czyszczenia zasobow wywoluje sie `unmount()` i sprawdza, czy efekty uboczne (listenery, timery, AbortController) zostaly usuniete. **6. Kiedy nalezy uzyc Pinia zamiast Composables z provide/inject?** Pinia jest preferowana, gdy stan musi byc globalnie dostepny z dowolnego komponentu bez relacji hierarchicznej, gdy wymagana jest persystencja (localStorage/sessionStorage), gdy potrzebna jest integracja z Vue DevTools do debugowania lub gdy wiele drzew komponentow musi wspoldzielic ten sam stan. Composables z `provide/inject` sa lepiej dopasowane do stanu kontekstowego ograniczonego do okreslonego poddrzewa komponentow. ## Podsumowanie Vue 3 Composables to znacznie wiecej niz alternatywa dla mixinow: stanowia paradygmat organizacji kodu, ktory skaluje sie od prostych funkcji pomocniczych po zlozone systemy z Dependency Injection i wielopoziomowa kompozycja. Opanowanie tych wzorcow odroznia programistow, ktorzy jedynie uzywaja Vue, od tych, ktorzy rozumieja system reaktywnosci w glab. Kluczowe wnioski: - **Czytelna struktura**: Typowane interfejsy wejscia i wyjscia (`UseXxxOptions` i `UseXxxReturn`) dokumentuja API i zapobiegaja przypadkowemu ujawnieniu wewnetrznych szczegolow implementacji - **Izolacja stanu**: Kazde wywolanie tworzy niezalezna instancje, calkowicie eliminujac konflikty - **Jawna kompozycja**: Composables konsumujace inne Composables tworca przewidywalne lancuchy reaktywnosci - **Zarzadzanie zasobami**: `AbortController` i `onUnmounted` gwarantuja czyszczenie efektow ubocznych i zapobiegaja wyciekom pamieci - **Typowana iniekcja**: `provide/inject` z `InjectionKey` zastepuje prop drilling bez utraty bezpieczenstwa typow - **Testowalnosc**: Wzorzec `withSetup` umozliwia walidacje reaktywnosci w pelnej izolacji - **Pragmatyzm**: Sprawdzenie VueUse przed reimplementacja typowych funkcjonalnosci oszczedza czas i wykorzystuje sprawdzone rozwiazania Przygotowanie tych wzorcow z konkretnymi implementacjami oraz zdolnoscia do wyjasnienia decyzji projektowych stojacych za kazdym z nich daje znaczaca przewage w technicznych procesach rekrutacyjnych na stanowiska Vue w 2026 roku. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/vue-nuxt/advanced-vue-3-composables-reusable-patterns