# Vue 3 z TypeScriptem w 2026: bezpieczne typowanie propsów, emitów i composables > Praktyczny przewodnik po typowaniu komponentów Vue 3 w TypeScript: generyczne defineProps, krotkowe defineEmits, typowane composables, defineModel oraz InjectionKey wraz z pytaniami rekrutacyjnymi. - Published: 2026-07-07 - Updated: 2026-07-07 - Author: SharpSkill - Tags: Vue 3, TypeScript, defineProps, Composables, Frontend - Reading time: 8 min --- Vue 3 w połączeniu z TypeScriptem w 2026 roku zapewnia pełną, statyczną kontrolę typów obejmującą propsy, zdarzenia oraz logikę wielokrotnego użytku. Składnia ` ``` Props `role` jest teraz ograniczony do dwóch literałów tekstowych, więc przekazanie `role="guest"` powoduje błąd kompilacji. To główny powód, by sięgać po `defineProps` z TypeScriptem: [oficjalny przewodnik Vue po TypeScript](https://vuejs.org/guide/typescript/composition-api.html) traktuje formę generyczną jako domyślną w projektach opartych na ` ``` Vue 3.5 ustabilizowało **reaktywną destrukturyzację propsów**, która jest dziś zwięźlejszym wzorcem. Destrukturyzacja wyniku `defineProps` i przypisanie wartości domyślnej w tej samej instrukcji pozostaje w pełni reaktywne — kompilator pod spodem przepisuje każdy odczyt z powrotem na `props.x`. ```vue ``` > **Pułapka reaktywności przy destrukturyzacji propsów** > > Zdestrukturyzowane propsy pozostają reaktywne w szablonie oraz w `computed`, ale przekazanie zdestrukturyzowanej wartości bezpośrednio do `watch` lub do composable odczytuje ją tylko raz i zrywa powiązanie reaktywne. Opakowuj taką wartość w getter — `watch(() => color, ...)` — albo konwertuj przez `toRef(props, 'color')`, gdy dalej potrzebny jest ref. To częsta pułapka rekrutacyjna: kandydaci zakładają, że zdestrukturyzowana zmienna jest zwykłą wartością, podczas gdy kompilator w rzeczywistości przekierował każdy odczyt z powrotem na `props.color`. ## Bezpieczne emity dzięki defineEmits Zdarzenia zasługują na tę samą staranność co propsy. Generyczne `defineEmits` opisuje każdą nazwę zdarzenia oraz jego ładunek w postaci krotki, dając komponentowi rodzica podpowiedzi kodu, a komponentowi dziecka kontrolę już w czasie kompilacji, że emitowane są właściwe argumenty. ```vue ``` Ta forma krotkowa zastąpiła starszą składnię sygnatur wywołania (`(e: 'search', q: string): void`), ponieważ czyta się ją lepiej i obsługuje wiele zdarzeń bez przeciążeń. Gdy zdarzenie nie niesie danych, pusta krotka `[]` dokumentuje to wprost. Połączenie typowanych emitów z typowanymi propsami daje komponenty, których cały publiczny interfejs jest weryfikowalny przed uruchomieniem — ta sama dyscyplina, którą opisuje [przewodnik po Composition API Vue](/blog/vue-nuxt/vue-3-composition-api-complete-guide). ## Typowanie composables dla logiki wielokrotnego użytku Composables to zwykłe funkcje, więc podlegają standardowym regułom TypeScript — ale kilka konwencji utrzymuje je ergonomicznymi. Zwracaj typy `Ref` jawnie, gdy wnioskowanie nie jest oczywiste, i sięgaj po typy generyczne, gdy composable opakowuje dowolne dane, takie jak odpowiedź API. ```ts // useFetch.ts import { ref, type Ref } from 'vue' interface UseFetchReturn { data: Ref error: Ref loading: Ref } // Generic flows through to the caller's typed data export function useFetch(url: string): UseFetchReturn { const data = ref(null) as Ref const error = ref(null) const loading = ref(true) fetch(url) .then((r) => r.json()) .then((json: T) => { data.value = json }) .catch((e: Error) => { error.value = e }) .finally(() => { loading.value = false }) return { data, error, loading } } ``` W miejscu wywołania parametr generyczny sprawia, że `data` jest w pełni otypowane bez dodatkowych adnotacji: ```ts // UserList.vue (script setup) interface User { id: number; name: string } // data is Ref — inferred from the generic const { data: users, loading } = useFetch('/api/users') ``` Jawne otypowanie zwracanego obiektu (`UseFetchReturn`) warte jest tych kilku dodatkowych linii: dokumentuje kontrakt, zapobiega przypadkowemu wyciekowi wewnętrznych refów i daje konsumentom jeden typ do zaimportowania. Głębsze wzorce composables, takie jak przeciążenia argumentów i sprzątanie powiązane z cyklem życia, omawia [zaawansowany przewodnik po composables Vue](/blog/vue-nuxt/advanced-vue-3-composables-reusable-patterns). ## Typowanie defineModel dla dwukierunkowego wiązania `defineModel`, stabilne od Vue 3.4, łączy dawny props `modelValue` oraz emit `update:modelValue` w jeden zapisywalny ref. Jego parametr generyczny typuje obie strony wiązania naraz. ```vue ``` Modele nazwane — `defineModel('title')` — mapują się na `v-model:title` i otrzymują to samo typowanie. Eliminuje to całą klasę błędów niezgodnego ładunku, które ręczny wzorzec props-plus-emit potrafił ukrywać. ## Typowanie refów szablonu i instancji komponentów Dostęp do węzła DOM lub komponentu potomnego przez `ref` to miejsce, w którym nieotypowany kod Vue najczęściej wraca do `any`. Rozwiązaniem jest sparametryzowanie `useTemplateRef` (Vue 3.5+) lub samego `ref` typem elementu, dzięki czemu dostęp do właściwości jest sprawdzany względem rzeczywistego interfejsu DOM. ```vue ``` Dla referencji do komponentu potomnego `InstanceType` wydobywa publiczny typ komponentu, udostępniając wszystko, co dziecko zadeklarowało przez `defineExpose`. Dzięki temu wywołania metod z rodzica do dziecka są w pełni sprawdzane, a nie zgadywane. ```vue ``` ## Bezpieczne provide i inject z InjectionKey Wstrzykiwanie zależności w drzewie komponentów traci informacje o typach, o ile klucz ich nie przenosi. `InjectionKey` to otypowany symbol, który wiąże typ wartości z jej kluczem, dzięki czemu `provide` i `inject` pozostają zgodne bez ręcznego rzutowania. ```ts // theme-key.ts import type { InjectionKey, Ref } from 'vue' export interface ThemeContext { mode: Ref<'light' | 'dark'> toggle: () => void } // The key permanently associates the ThemeContext type with this symbol export const ThemeKey: InjectionKey = Symbol('theme') ``` ```ts // provider (script setup) — value must match ThemeContext or it fails to compile provide(ThemeKey, { mode, toggle }) // consumer — theme is inferred as ThemeContext | undefined const theme = inject(ThemeKey) theme?.toggle() ``` Przekazanie wartości, której kształt nie pasuje do `ThemeContext`, jest błędem kompilacji w miejscu wstrzyknięcia, co wychwytuje rozjazd między odległymi komponentami, zanim trafi na produkcję. ## Typowe pytania rekrutacyjne o Vue i TypeScript Rekruterzy sprawdzają, czy kandydat rozumie granicę między typami w czasie kompilacji a zachowaniem w czasie działania. Kilka powracających pytań i ich trafne odpowiedzi: - **Dlaczego preferować `defineProps()` zamiast formy obiektowej czasu działania?** Generyczne propsy wyrażają typy unijne, sygnatury funkcji i zagnieżdżone kształty, których deklaracje czasu działania nie potrafią oddać, a przy tym usuwają zdublowane definicje typów i wartości runtime. - **Czy zdestrukturyzowane propsy są reaktywne?** Tak, w Vue 3.5+, ponieważ kompilator przepisuje dostęp na `props.x`. Ale zdestrukturyzowane *wartości* przekazane do `watch` lub composable są odczytywane raz — użyj gettera albo `toRef`. - **Jak otypować emitowane zdarzenie bez ładunku?** Pustą krotką: `defineEmits<{ close: [] }>()`. - **Co zastępuje `defineModel`?** Parę: props `modelValue` oraz emit `update:modelValue`, zjednoczoną w jeden otypowany, zapisywalny ref. Ćwiczenie tych zagadnień na prawdziwej bazie pytań wyostrza odruchy, które doceniają rozmowy rekrutacyjne — [moduł pytań rekrutacyjnych o composables Vue](/technologies/vue-nuxt/interview-questions/vue-composables) trenuje dokładnie te wzorce. Narzędzia też mają znaczenie: uruchamiaj `vue-tsc` w CI, aby błędy typów blokowały scalanie, i sięgaj po [podręcznik TypeScript](https://www.typescriptlang.org/docs/handbook/2/generics.html), gdy w grę wchodzą generyczne composables. Doświadczenie w edytorze napędza [oficjalne narzędzie Volar od Vue](https://github.com/vuejs/language-tools), które odczytuje te makra i pokazuje błędy w miejscu ich powstania. ## Podsumowanie - Używaj generycznego `defineProps()`, aby wyrazić typy unijne, pola opcjonalne i propsy będące callbackami, których deklaracje czasu działania nie potrafią uchwycić. - Preferuj reaktywną destrukturyzację propsów z wartościami domyślnymi w linii w Vue 3.5+, a po `withDefaults` sięgaj tylko wtedy, gdy wspólny obiekt wartości domyślnych jest czytelniejszy. - Typuj zdarzenia formą krotkową `defineEmits`, aby ładunki były sprawdzane w czasie kompilacji zarówno w dziecku, jak i w rodzicu. - Adnotuj typy zwracane composables jawnie i używaj typów generycznych, by przekazywać typy danych dostarczone przez wywołującego od początku do końca. - Wybieraj `defineModel()` dla wiązania dwukierunkowego, aby zwinąć szablonowy kod props-plus-emit w jeden otypowany ref. - Uruchamiaj `vue-tsc` w ciągłej integracji, aby regresje typów zatrzymywały build, zamiast trafiać na produkcję. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/vue-nuxt/vue-3-typescript-type-safe-props-emits-composables