# Vue 3 con TypeScript nel 2026: props, emits e composable type-safe > Padroneggiare componenti Vue 3 type-safe con TypeScript: defineProps generico, defineEmits a tupla, composable tipizzati, defineModel e InjectionKey, con domande da colloquio. - Published: 2026-07-07 - Updated: 2026-07-07 - Author: SharpSkill - Tags: Vue 3, TypeScript, defineProps, Composables, Frontend - Reading time: 8 min --- Nel 2026 Vue 3 con TypeScript offre un controllo dei tipi completamente statico su props, eventi e logica riutilizzabile. La sintassi ` ``` La prop `role` è ora vincolata a due stringhe letterali, per cui passare `role="guest"` fa fallire la build. È proprio questo il motivo principale per ricorrere a `defineProps` con TypeScript: la [guida ufficiale a Vue e TypeScript](https://vuejs.org/guide/typescript/composition-api.html) tratta la forma generica come impostazione predefinita per i progetti con ` ``` Vue 3.5 ha stabilizzato la **destrutturazione reattiva delle props**, ormai il pattern più conciso. Destrutturare il risultato di `defineProps` e assegnare un valore predefinito nella stessa istruzione mantiene tutto pienamente reattivo: il compilatore riscrive ogni accesso a `props.x` dietro le quinte. ```vue ``` > **Trappola di reattività con le props destrutturate** > > Le props destrutturate restano reattive nel template e in `computed`, ma passare un valore destrutturato direttamente a `watch` o a un composable lo legge una sola volta e interrompe il collegamento reattivo. In questi casi conviene racchiuderlo in un getter — `watch(() => color, ...)` — oppure convertirlo con `toRef(props, 'color')` quando a valle serve una ref. Questa è una trappola frequente nei colloqui: chi si candida presume che la variabile destrutturata sia un valore semplice, mentre in realtà il compilatore ha reindirizzato ogni lettura a `props.color`. ## Eventi type-safe con defineEmits Gli eventi meritano lo stesso rigore delle props. Il generico `defineEmits` descrive ogni nome di evento e il relativo payload come una tupla, offrendo al componente genitore il completamento automatico e al componente figlio i controlli in fase di compilazione che gli argomenti emessi siano quelli corretti. ```vue ``` Questa forma a tupla ha sostituito la vecchia sintassi con firma di chiamata (`(e: 'search', q: string): void`) perché si legge meglio e supporta più eventi senza overload. Quando un evento non trasporta dati, una tupla vuota `[]` lo documenta in modo esplicito. Abbinare eventi tipizzati a props tipizzate produce componenti la cui intera interfaccia pubblica è verificabile prima del runtime — la stessa disciplina trattata nella [guida alla Composition API di Vue](/blog/vue-nuxt/vue-3-composition-api-complete-guide). ## Tipizzare i composable per la logica riutilizzabile I composable sono semplici funzioni, quindi seguono le normali regole di TypeScript — ma alcune convenzioni li mantengono ergonomici. Conviene restituire esplicitamente i tipi `Ref` quando l'inferenza non è ovvia e usare i generici quando un composable incapsula dati arbitrari come una risposta 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 } } ``` Nel punto di chiamata il parametro generico rende `data` completamente tipizzato senza alcuna annotazione aggiuntiva: ```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') ``` Annotare esplicitamente l'oggetto restituito (`UseFetchReturn`) vale le poche righe in più: documenta il contratto, evita fughe accidentali di ref interne e offre a chi consuma il composable un unico tipo da importare. Per pattern di composable più avanzati come overload degli argomenti e cleanup consapevole del ciclo di vita, si veda la [guida avanzata ai composable di Vue](/blog/vue-nuxt/advanced-vue-3-composables-reusable-patterns). ## Tipizzare defineModel per il binding bidirezionale `defineModel`, stabile da Vue 3.4, riduce la vecchia coppia formata dalla prop `modelValue` e dall'emit `update:modelValue` a un'unica ref scrivibile. Il suo parametro generico tipizza entrambe le direzioni del binding in un colpo solo. ```vue ``` I modelli con nome — `defineModel('title')` — vengono mappati su `v-model:title` e ricevono la stessa tipizzazione. Questo elimina un'intera categoria di bug da payload non corrispondenti che il pattern manuale prop-più-emit tendeva a nascondere. ## Tipizzare le template ref e le istanze di componente Accedere a un nodo del DOM o a un componente figlio tramite una `ref` è il punto in cui il codice Vue non tipizzato più spesso ripiega su `any`. La soluzione è parametrizzare `useTemplateRef` (Vue 3.5+) o la `ref` stessa con il tipo dell'elemento, così che l'accesso alle proprietà venga controllato rispetto alla reale interfaccia del DOM. ```vue ``` Per un riferimento a un componente figlio, `InstanceType` estrae il tipo pubblico del componente, esponendo tutto ciò che il figlio ha dichiarato tramite `defineExpose`. In questo modo le chiamate di metodo dal genitore al figlio restano completamente controllate anziché indovinate. ```vue ``` ## provide e inject type-safe con InjectionKey L'iniezione di dipendenze attraverso l'albero dei componenti perde le informazioni di tipo a meno che la chiave non le trasporti. `InjectionKey` è un symbol tipizzato che lega il tipo di un valore alla sua chiave, così `provide` e `inject` restano sincronizzati senza cast manuali. ```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() ``` Fornire un valore la cui struttura non corrisponde a `ThemeContext` è un errore di compilazione nel punto di iniezione, intercettando così le divergenze tra componenti distanti prima che vengano rilasciate. ## Domande frequenti nei colloqui su Vue e TypeScript Chi conduce i colloqui verifica se la persona candidata comprende il confine tra i tipi in fase di compilazione e il comportamento a runtime. Alcune domande ricorrenti e le loro risposte incisive: - **Perché preferire `defineProps()` alla forma con oggetto a runtime?** Le props generiche esprimono tipi union, firme di funzione e strutture annidate che le dichiarazioni a runtime non possono rappresentare, ed eliminano le definizioni duplicate di tipo e runtime. - **Le props destrutturate sono reattive?** Sì, in Vue 3.5+, perché il compilatore riscrive l'accesso a `props.x`. Ma i *valori* destrutturati passati a `watch` o a un composable vengono letti una sola volta — occorre un getter o `toRef`. - **Come si tipizza un evento emesso senza payload?** Con una tupla vuota: `defineEmits<{ close: [] }>()`. - **Che cosa sostituisce `defineModel`?** La coppia formata dalla prop `modelValue` e dall'emit `update:modelValue`, unificata in un'unica ref scrivibile tipizzata. Esercitarsi su queste domande con una vera banca di quesiti affina i riflessi che i colloqui premiano — il [modulo di colloquio sui composable di Vue](/technologies/vue-nuxt/interview-questions/vue-composables) allena esattamente questi pattern. Anche il tooling conta: eseguire `vue-tsc` nella CI affinché gli errori di tipo blocchino i merge, e appoggiarsi al [manuale di TypeScript](https://www.typescriptlang.org/docs/handbook/2/generics.html) quando entrano in gioco i generici dei composable. L'esperienza nell'editor è alimentata dal [tooling ufficiale Volar di Vue](https://github.com/vuejs/language-tools), che legge queste macro per segnalare gli errori inline. ## Conclusione - Usare il generico `defineProps()` per esprimere tipi union, campi opzionali e props di callback che le dichiarazioni a runtime non riescono a catturare. - In Vue 3.5+ preferire la destrutturazione reattiva delle props con valori predefiniti inline, e ricorrere a `withDefaults` solo quando un oggetto condiviso di valori predefiniti risulta più chiaro. - Tipizzare gli eventi con la forma a tupla di `defineEmits` così che i payload vengano controllati in fase di compilazione sia nel figlio sia nel genitore. - Annotare esplicitamente i tipi di ritorno dei composable e usare i generici per inoltrare da un capo all'altro i tipi di dati forniti da chi chiama. - Adottare `defineModel()` per il binding bidirezionale, così da ridurre il boilerplate prop-più-emit a un'unica ref tipizzata. - Eseguire `vue-tsc` nell'integrazione continua affinché le regressioni di tipo facciano fallire la build invece di raggiungere la produzione. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/it/blog/vue-nuxt/vue-3-typescript-type-safe-props-emits-composables