# Vue 3 з TypeScript у 2026: типобезпечні props, emits та composables > Практичний посібник із типізації компонентів Vue 3 у TypeScript: узагальнені defineProps, кортежні defineEmits, типізовані composables, defineModel та InjectionKey разом із питаннями для співбесід. - Published: 2026-07-07 - Updated: 2026-07-07 - Author: SharpSkill - Tags: Vue 3, TypeScript, defineProps, Composables, Frontend - Reading time: 8 min --- Vue 3 у поєднанні з TypeScript у 2026 році забезпечує повну статичну перевірку типів для props, подій та повторно використовуваної логіки. Синтаксис ` ``` Props `role` тепер обмежений двома рядковими літералами, тож передавання `role="guest"` спричиняє помилку збірки. Це головна причина застосовувати `defineProps` з TypeScript: [офіційний посібник Vue з TypeScript](https://vuejs.org/guide/typescript/composition-api.html) вважає узагальнену форму типовою для проєктів на ` ``` Vue 3.5 стабілізувало **реактивну деструктуризацію props**, яка сьогодні є лаконічнішим патерном. Деструктуризація результату `defineProps` і присвоєння значення за замовчуванням у тій самій інструкції залишається повністю реактивною — компілятор під капотом переписує кожне звернення назад на `props.x`. ```vue ``` > **Пастка реактивності при деструктуризації props** > > Деструктуризовані props залишаються реактивними в шаблоні та в `computed`, але передавання деструктуризованого значення безпосередньо у `watch` чи у composable зчитує його лише раз і розриває реактивний зв’язок. Огортайте таке значення в геттер — `watch(() => color, ...)` — або перетворюйте через `toRef(props, 'color')`, коли далі потрібен ref. Це поширена пастка на співбесідах: кандидати вважають деструктуризовану змінну звичайним значенням, тоді як компілятор насправді перенаправив кожне зчитування назад на `props.color`. ## Типобезпечні emits за допомогою defineEmits Події заслуговують на ту саму ретельність, що й props. Узагальнений `defineEmits` описує кожну назву події та її корисне навантаження у вигляді кортежу, надаючи батьківському компоненту автодоповнення, а дочірньому — перевірку на етапі компіляції, що передаються правильні аргументи. ```vue ``` Ця кортежна форма замінила старіший синтаксис сигнатур виклику (`(e: 'search', q: string): void`), бо читається краще й підтримує кілька подій без перевантажень. Коли подія не несе даних, порожній кортеж `[]` документує це явно. Поєднання типізованих emits із типізованими props дає компоненти, весь публічний інтерфейс яких перевіряється до виконання, — та сама дисципліна, що описана в [посібнику з Composition API Vue](/blog/vue-nuxt/vue-3-composition-api-complete-guide). ## Типізація composables для повторно використовуваної логіки Composables — це звичайні функції, тож вони підпорядковуються стандартним правилам TypeScript, але кілька домовленостей роблять їх ергономічними. Повертайте типи `Ref` явно, коли виведення неочевидне, і використовуйте узагальнення, коли composable огортає довільні дані, наприклад відповідь 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 } } ``` У місці виклику узагальнений параметр робить `data` повністю типізованим без жодної додаткової анотації: ```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') ``` Явна анотація об’єкта, що повертається (`UseFetchReturn`), варта цих кількох додаткових рядків: вона документує контракт, запобігає випадковому витоку внутрішніх ref і дає споживачам єдиний тип для імпорту. Про глибші патерни composables, як-от перевантаження аргументів і очищення, прив’язане до життєвого циклу, читайте в [розширеному посібнику з composables Vue](/blog/vue-nuxt/advanced-vue-3-composables-reusable-patterns). ## Типізація defineModel для двостороннього зв’язування `defineModel`, стабільний із Vue 3.4, згортає колишній props `modelValue` та emit `update:modelValue` в єдиний записуваний ref. Його узагальнений параметр типізує обидва напрямки зв’язування одразу. ```vue ``` Іменовані моделі — `defineModel('title')` — відображаються на `v-model:title` й отримують ту саму типізацію. Це усуває цілий клас помилок невідповідного навантаження, які ручний патерн props-плюс-emit зазвичай приховував. ## Типізація ref шаблонів та екземплярів компонентів Доступ до вузла DOM чи дочірнього компонента через `ref` — це місце, де нетипізований код Vue найчастіше повертається до `any`. Рішення — параметризувати `useTemplateRef` (Vue 3.5+) або сам `ref` типом елемента, щоб доступ до властивостей перевірявся щодо справжнього інтерфейсу DOM. ```vue ``` Для посилання на дочірній компонент `InstanceType` витягує публічний тип компонента, відкриваючи все, що дочірній компонент оголосив через `defineExpose`. Це тримає виклики методів від батька до дитини повністю перевіреними, а не вгаданими. ```vue ``` ## Типобезпечні provide та inject з InjectionKey Впровадження залежностей у дереві компонентів втрачає інформацію про типи, якщо ключ її не переносить. `InjectionKey` — це типізований символ, що прив’язує тип значення до його ключа, тож `provide` та `inject` залишаються узгодженими без ручного приведення. ```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() ``` Надання значення, форма якого не відповідає `ThemeContext`, є помилкою компіляції в місці впровадження, що вловлює розбіжність між віддаленими компонентами ще до потрапляння в продакшн. ## Типові питання для співбесід із Vue та TypeScript Співрозмовники перевіряють, чи розуміє кандидат межу між типами часу компіляції та поведінкою під час виконання. Кілька повторюваних питань і чіткі відповіді на них: - **Чому варто надавати перевагу `defineProps()` перед об’єктною формою часу виконання?** Узагальнені props виражають об’єднані типи, сигнатури функцій та вкладені форми, яких оголошення часу виконання не можуть відтворити, і водночас усувають дубльовані визначення типів і часу виконання. - **Чи є деструктуризовані props реактивними?** Так, у Vue 3.5+, бо компілятор переписує доступ на `props.x`. Але деструктуризовані *значення*, передані у `watch` чи composable, зчитуються один раз — використовуйте геттер або `toRef`. - **Як типізувати подію без корисного навантаження?** Порожнім кортежем: `defineEmits<{ close: [] }>()`. - **Що замінює `defineModel`?** Пару: props `modelValue` та emit `update:modelValue`, об’єднану в один типізований записуваний ref. Відпрацювання цих тем на справжній базі питань загострює рефлекси, які цінують співбесіди, — [модуль питань для співбесід із composables Vue](/technologies/vue-nuxt/interview-questions/vue-composables) тренує саме ці патерни. Інструменти теж важливі: запускайте `vue-tsc` у CI, щоб помилки типів блокували злиття, і звертайтеся до [довідника TypeScript](https://www.typescriptlang.org/docs/handbook/2/generics.html), коли до справи долучаються узагальнення composables. Досвід у редакторі забезпечує [офіційний інструментарій Volar від Vue](https://github.com/vuejs/language-tools), який читає ці макроси й показує помилки прямо в коді. ## Висновок - Використовуйте узагальнений `defineProps()`, щоб виразити об’єднані типи, необов’язкові поля та props-колбеки, яких оголошення часу виконання не здатні охопити. - Надавайте перевагу реактивній деструктуризації props із вбудованими значеннями за замовчуванням у Vue 3.5+, а до `withDefaults` вдавайтеся лише тоді, коли спільний об’єкт значень за замовчуванням зрозуміліший. - Типізуйте події кортежною формою `defineEmits`, щоб навантаження перевірялися на етапі компіляції як у дитині, так і в батькові. - Явно анотуйте типи, що повертають composables, і використовуйте узагальнення, щоб наскрізно передавати надані викликачем типи даних. - Застосовуйте `defineModel()` для двостороннього зв’язування, щоб згорнути шаблонний код props-плюс-emit в один типізований ref. - Запускайте `vue-tsc` у безперервній інтеграції, щоб регресії типів зупиняли збірку, а не потрапляли в продакшн. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/vue-nuxt/vue-3-typescript-type-safe-props-emits-composables