# 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 `
{{ props.name }}
{{ props.role }}
```
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
{{ label }}
```
> **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