# TypeScript: interface o type per le props di un componente

Le due forme non sono intercambiabili, ma la differenza conta in pochi casi. Ecco la regola che tiene il codice leggibile: type per le props, interface per un'API che altri devono estendere.

> Pagina: https://ui.eneawork.it/blog/typescript-interface-o-type-props
> Markdown: https://ui.eneawork.it/blog/typescript-interface-o-type-props.md

*2026-11-01*

Risposta breve: per le props di un componente usa type. Per un'API che altre squadre devono estendere, usa interface. Il resto è preferenza, e la preferenza non deve cambiare da file a file.

La domanda «Interfaces vs Types in TypeScript» vale 2813 voti su Stack Overflow (letto il 31 ottobre 2026 con la Stack Exchange API): fonte. Il catalogo EneUI misura 111.985 componenti, 1.556 blocchi, 12.808 icone da 1.472 librerie, letti da /api/stats il 1 novembre 2026, e verificati con una chiamata diretta all'endpoint. Ogni comando qui sotto è eseguibile in locale: nessun passaggio richiede un account.

## Il meccanismo: registro dei tipi contro espressione di tipo

Una interface introduce un nome nel registro dei tipi e lo lascia aperto: chiunque può aggiungere una seconda dichiarazione con lo stesso nome e il compilatore le fonde. Un type lega un nome a un'espressione di tipo: la espressione può essere qualsiasi cosa, la dichiarazione è chiusa e una seconda con lo stesso nome è un errore.

Sul caso semplice il compilatore risolve entrambe nella stessa struttura e l'assegnabilità è identica. La differenza compare quando la forma deve esprimere una scelta o quando deve restare aperta: sono i due assi che contano davvero.

## Le differenze che contano

L'indice implicito merita una riga: un type literal è assegnabile a un indice solo se non dichiara proprietà incompatibili, mentre un'interfaccia non riceve mai un indice implicito. È il motivo per cui i due costrutti si comportano in modo diverso quando tipizzi la risposta di un'API.

## La forma consigliata per le props

type Tone = "ok" | "warn" | "danger"; type AlertProps = { tone: Tone; title: string; children?: React.ReactNode; }; export function Alert({ tone, title, children }: AlertProps) { return ( {title} {children} ); }

Il tipo derivato da una union impedisce di passare una stringa arbitraria: l'errore arriva in editor, non a runtime. Con una interface dichiareresti tone: string e perderesti il controllo, perché una union non entra in una clausola extends.

## Dove vince interface

// packages/ui/src/theme.ts export interface Theme { colors: Record; } // un'app può aggiungere campi senza modificare il pacchetto declare module "@acme/ui" { interface Theme { radii?: Record; } }

Il declaration merging è utile per i punti di estensione pubblici. È anche il motivo per cui non vuoi usarlo per le props interne: due dichiarazioni con lo stesso nome si sommano e un campo rimosso altrove continua a esistere.

## Diagnosi: cerca il cast, non la parola chiave

Quando TypeScript accetta un valore sbagliato, la forma della dichiarazione non c'entra. Il tipo è stato allargato da qualche parte: un as, un any implicito, un parametro senza annotazione. Cerca i cast prima di riscrivere il tipo.

grep -rn " as [A-Za-z]\+Props\b" src | head npx tsc --noEmit --strict

Il secondo comando dice se strict è attivo. Con strictNullChecks spento, una prop opzionale e una obbligatoria sono indistinguibili, e nessuna delle due forme può proteggerti. La prova è un esperimento: togli il cast, ricompila, guarda dove arriva l'errore. Se l'errore si sposta sul chiamante, il difetto era il cast.

## Quando la correzione comune peggiora le cose

La prima scorciatoia è cambiare parola chiave per far sparire un messaggio. Su una union non compila, quindi si torna indietro e si spezza la union in tre tipi: ora il codice passa e la scelta fra i tre stati non è più verificata dal compilatore. Il messaggio era l'unico posto in cui la regola era scritta.

La seconda è aggiungere un'index signature o un'intersezione con Record<string, unknown> per chiudere un errore di proprietà sconosciuta. Da quel momento ogni chiave è valida: gli errori di battitura tornano a runtime e l'autocompletamento smette di essere un elenco.

## Un limite da mettere in conto: il merging è globale

Il declaration merging si applica al programma, non al file. Due pacchetti che augmentano lo stesso Theme si sommano ovunque, anche nei test e nelle build di altri team. Se un modulo rende obbligatorio un campo che un altro non conosce, la rottura arriva lontano dal punto in cui è stata introdotta. Con un type lo stesso conflitto è un errore immediato, ed è il compromesso che paghi quando scegli l'estensione aperta.

## Come prevenirlo

## Dove continuare

Nel catalogo: componenti, dashboard con shell e blocchi. Le guide: TypeScript o JavaScript per la UI e UI kit, libreria o design system; il confronto: librerie React misurate.

## FAQ

### Quale delle due è più veloce da compilare?

La differenza si misura solo su progetti enormi, quindi non è un criterio di scelta. Prima viene la capacità di esprimere il tipo, poi la leggibilità per chi arriva dopo.

### Perché `interface` accetta una seconda dichiarazione con lo stesso nome?

È il declaration merging, pensato per aggiungere campi a tipi esistenti. Con `type` la seconda dichiarazione è un errore: è una protezione, non un limite.

### Posso usare `interface` con una union?

No, una union richiede un alias di tipo: `type Tone = "ok" | "warn" | "danger"`. La clausola `extends` di un'interfaccia accetta solo un nome o un elenco di nomi.

### Cosa cambia per i tipi degli eventi?

Nulla: arrivano da React e non li dichiari tu. La scelta fra i due costrutti riguarda soltanto le forme che scrivi nel progetto.

### Serve `React.FC`?

No, e molte codebase lo stanno togliendo. Tipare le props e restituire `ReactNode` è più diretto e non aggiunge comportamenti impliciti come `children`.
