Menu

Interface vs type in TypeScript: differenze e quando usarli

interface e type possono entrambi descrivere forme di oggetti, e quasi sempre vanno bene tutti e due. Scopri le differenze reali: declaration merging, unioni e mapped types, extends e intersezioni, index signature implicite, messaggi di errore e prestazioni del compilatore, più una regola chiara per scegliere.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Per descrivere la forma di un oggetto, interface e type fanno lo stesso lavoro, e i valori dell'uno sono assegnabili all'altro quando le forme coincidono. Le differenze stanno ai margini: type può dare un nome a cose che un'interfaccia non può esprimere (unioni, tuple, tipi calcolati), e un'interfaccia può fare alcune cose che un alias di tipo non sa fare (merging, extends controllato).

TypeScript confronta i tipi oggetto in base alla struttura, quindi per l'assegnabilità il nome della dichiarazione non conta. Di seguito trovi l'elenco dei casi in cui la scelta conta davvero.

Tabella di confronto

Funzionalitàinterfacetype
Forme di oggettisìsì
Facoltative, readonly, metodi, index signaturesìsì
Genericssìsì
Unioni (A | B)nosì
Tuple, primitivi, tipi funzione da solino (tipi funzione solo come call signature)sì
Mapped e conditional typesnosì
Estensioneextends, i conflitti sono errori&, i conflitti diventano never
Declaration mergingsìno (identificatore duplicato)
Assegnabile a Record<string, T>nosì, quando le proprietà sono compatibili
implements in una classesìsì, se è un tipo oggetto
Definizioni ricorsivesìsì

Cosa sa fare solo type

Tutto ciò che non è una singola forma di oggetto ha bisogno di un alias di tipo:

Nessuno di questi si può scrivere con interface (gli ultimi due sono spiegati in mapped types e conditional types). È il motivo pratico per cui ogni codebase finisce per usare type da qualche parte, qualunque cosa usi per le forme di oggetti.

Cosa sa fare solo interface: il declaration merging

Due dichiarazioni interface con lo stesso nome nello stesso scope si uniscono in una. Due dichiarazioni type con lo stesso nome danno l'errore TS2300, Duplicate identifier.

interface Settings {
  theme: string;
}
interface Settings {
  fontSize: number;
}
const s: Settings = { theme: "dark", fontSize: 14 }; // needs both

type Options = { a: number };
type Options = { b: number }; // error TS2300: Duplicate identifier 'Options'.

Il merging è il modo in cui i tipi delle librerie vengono estesi dall'esterno: aggiungere una proprietà al Window globale, alla Request di Express o al tipo del tema di una libreria. Se pubblichi tipi che gli utenti potrebbero dover estendere, usa le interfacce. Nel codice della tua applicazione, un merge accidentale (due file di script che dichiarano la stessa interfaccia globale) avviene senza avvisi, a meno che le due non dichiarino la stessa proprietà con tipi diversi: è uno degli argomenti con cui alcuni team preferiscono type.

extends o intersezione

Un'interfaccia si estende con extends, un alias di tipo con &. Il risultato è quasi sempre lo stesso, ma gestiscono in modo diverso una proprietà in conflitto. extends segnala il conflitto nella dichiarazione:

index.ts(7,11): error TS2430: Interface 'Broken' incorrectly extends interface 'Base'.
  Types of property 'id' are incompatible.
    Type 'number' is not assignable to type 'string'.

Un'intersezione accetta lo stesso conflitto senza avvisare e trasforma la proprietà in never (una string che è anche un number). L'errore compare solo più tardi, quando provi a creare un valore:

L'errore punta all'oggetto, lontano dalla dichiarazione che l'ha causato. Per costruire tipi oggetto a partire da altri tipi oggetto, extends dà il messaggio migliore.

Index signature: una differenza che sfugge facilmente

Un alias di tipo per un tipo oggetto riceve una index signature implicita, quindi si può passare dove è atteso un Record<string, unknown>. Un'interfaccia no. È una scelta voluta: la spiegazione del team di TypeScript (issue #15300 nel repository di TypeScript) è che un'interfaccia può essere estesa da dichiarazioni successive, quindi dedurre per essa una index signature è meno sicuro, e cambiare ora la regola romperebbe troppo codice.

La chiamata marcata @ts-expect-error viene comunque eseguita e stampa i campi di Alan: la regola vale solo in compilazione. È il motivo tipico di un errore poco chiaro quando un valore di un'interfaccia viene passato a un helper di logging, serializzazione o query tipizzato con Record<string, ...>. Trasforma quella dichiarazione in type, fai uno spread del valore, oppure tipizza il parametro dell'helper con un'interfaccia o un generic.

Prestazioni e messaggi di errore

La pagina Performance della wiki di TypeScript (sezione "Preferring Interfaces Over Intersections") suggerisce interface Foo extends Bar, Baz { ... } invece di type Foo = Bar & Baz & { ... } quando si compongono tipi oggetto. I suoi motivi: un'interfaccia è un unico tipo oggetto piatto che rileva i conflitti tra proprietà, le relazioni di tipo tra interfacce vengono messe in cache (le intersezioni nel loro insieme no), e controllare un valore rispetto a un'intersezione controlla ogni componente prima del tipo appiattito. La differenza conta nelle codebase grandi con molti tipi composti; per poche normali forme di oggetti non la misurerai.

La stessa pagina osserva che le interfacce vengono visualizzate meglio. Un'interfaccia viene mostrata con il suo nome nei tooltip e nei messaggi di errore, mentre un alias di intersezione viene spesso stampato espanso nelle sue parti, e questo rende più difficili da leggere i messaggi lunghi.

Quale usare

Una regola che funziona:

  1. Forme di oggetti: interface. Ti dà un extends controllato, errori più chiari nelle composizioni grandi, e permette agli utenti di una libreria di estenderla. Corrisponde al criterio dell'handbook di TypeScript: "usa interface finché non ti servono funzionalità di type".
  2. Tutto il resto: type. Unioni, tuple, tipi funzione, tipi letterali e tutto ciò che si costruisce con mapped, conditional o template literal types.
  3. Eccezione: usa type per una forma di oggetto che deve adattarsi a parametri Record<string, ...>, o quando vuoi impedire di proposito il merging.

Usare type per tutto è anch'essa una scelta coerente, e molte codebase lo fanno. Mescolare i due a caso è l'unica opzione da evitare: fa chiedere a chi legge se una differenza fosse voluta.

Domande frequenti

Che differenza c'è tra type e interface in TypeScript?

Entrambi descrivono forme di oggetti e per questo sono intercambiabili. type può anche dare un nome a unioni, tuple, primitivi e mapped o conditional types, cosa che interface non può fare. interface supporta il declaration merging ed extends, che controlla le proprietà in conflitto. I tipi oggetto scritti con type sono anche assegnabili a tipi con index signature come Record<string, unknown>, mentre le interfacce no.

Meglio usare type o interface?

Il criterio dell'handbook di TypeScript è: usa interface finché non ti serve una funzionalità che ha solo type. In pratica significa interfacce per le forme di oggetti e type per unioni, tuple, tipi funzione e tipi calcolati. Anche i team che usano type per tutto si trovano bene; la cosa importante è una regola coerente.

interface è più veloce di type in TypeScript?

Nel comporre tipi oggetto, in alcuni casi sì. Le indicazioni sulle prestazioni del team di TypeScript consigliano interface ... extends rispetto a grandi intersezioni (A & B & { ... }), perché le relazioni tra interfacce vengono messe in cache e un'interfaccia è un unico tipo piatto. Per una semplice forma di oggetto non c'è una differenza significativa.

Una classe può implementare un alias di tipo?

Sì, se l'alias è un tipo oggetto (o un'intersezione di tipi oggetto): class Point implements PointType { ... } funziona. Una classe non può implementare un tipo unione; è l'errore TS2422.

Un'interfaccia può estendere un alias di tipo?

Sì, purché l'alias sia un tipo oggetto: type Base = { id: string }; interface User extends Base { name: string } è valido. Non può estendere un alias unione. Nella direzione opposta, un alias di tipo può costruire su un'interfaccia con &.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA