Interfaccia TypeScript e tipo: Differenze reali, testate con il compilatore
Interfacce e alias di tipo si sovrappongono così tanto che la scelta spesso sembra arbitraria. Ecco cosa può fare ciascuno e l’altro non può, confermato da errori reali del compilatore, oltre a una semplice regola che il tuo team può seguire.
- Per le forme di oggetti semplici, un’interfaccia e un alias di tipo sono quasi intercambiabili.
- Solo
typepuò nominare unioni, tuple e altri tipi non oggetti. - Solo
interfacesupporta la unione di dichiarazioni, dove due dichiarazioni si combinano. - Una regola comune: interfacce per le forme degli oggetti,
typeper tutto il resto, applicate in modo coerente.
Poche domande su TypeScript provocano tanto dibattito quanto Interfaccia TypeScript e tipo. Molti articoli elencano le differenze che erano importanti nelle versioni precedenti ma che ora non sono aggiornate. Quindi, invece di ripetere le affermazioni, le abbiamo testate.
Abbiamo scritto un file esercitando ciascuna differenza e lo abbiamo controllato con TypeScript 5.6.3 in modalità rigorosa. Gli errori del compilatore citati di seguito provengono da quell’esecuzione. Il comportamento può cambiare tra le versioni, quindi verifica rispetto alla versione TypeScript utilizzata dal tuo progetto. Se non conosci il linguaggio, inizia con il nostro tutorial TypeScript per principianti.
La risposta breve
Un interface descrive la forma di un oggetto e può essere esteso o riaperto. Un alias type dà un nome a qualsiasi tipo, inclusi oggetti, unioni, tuple e tipi di funzione. Laddove entrambi possono funzionare, la differenza è principalmente nello stile.
Dove sono gli stessi
Queste due dichiarazioni descrivono forme identiche:
interface User {
id: number;
name: string;
email?: string;
}
type UserAlias = {
id: number;
name: string;
email?: string;
};
const a: User = { id: 1, name: "Asha" };
const b: UserAlias = a; // no error
TypeScript utilizza la tipizzazione strutturale, il che significa che confronta forme anziché nomi. Ecco perché un User può essere assegnato a un UserAlias senza lamentarsi. Entrambi supportano proprietà opzionali, readonly proprietà, metodi e firme di indice.
Interfaccia e tipo fianco a fianco
| Caratteristica | interfaccia | tipo |
|---|---|---|
| Forme degli oggetti | Sì | Sì |
Sindacati, come "a" | "b" |
No | Sì |
| Tuple e primitive | No | Sì |
| Tipi mappati e condizionali | No | Sì |
| Fusione delle dichiarazioni | Sì | No, è un errore |
| Estendendo | si estende |
& incrocio |
| Proprietà contrastanti | Errore nella dichiarazione | Può tranquillamente diventare never |
Utilizzato con implements |
Sì | Sì, se si tratta di un tipo di oggetto |
Ciò che solo il tipo può fare
Molti tipi utili non sono affatto forme di oggetti e solo un alias di tipo può nominarli:
type Status = "pending" | "paid" | "shipped"; // union
type Point = [number, number]; // tuple
type Handler = (event: string) => void; // function type
type Keys = keyof User; // computed type
type ReadonlyUser = Readonly<User>; // utility type
I tipi di unione sono tra le funzionalità più preziose di TypeScript, perché il compilatore rifiuta quindi qualsiasi valore esterno all’elenco:
let s: Status = "paid";
s = "refunded";
// error TS2322: Type '"refunded"' is not assignable to type 'Status'.
Ciò rileva errori di battitura e valori obsoleti in fase di compilazione, prima che raggiungano la produzione.
Fusione delle dichiarazioni
Se dichiari due volte un’interfaccia con lo stesso nome, TypeScript le unisce in una sola:
interface Settings {
theme: string;
}
interface Settings {
language: string;
}
const settings: Settings = { theme: "dark", language: "en" }; // no error
Prova la stessa cosa con un alias di tipo e il compilatore rifiuta:
type Config = { theme: string };
type Config = { language: string };
// error TS2300: Duplicate identifier 'Config'.
L’unione è il modo in cui le librerie ti consentono di estendere i loro tipi. Ad esempio, puoi aggiungere una proprietà personalizzata all’interfaccia globale Window o all’oggetto richiesta di un framework, senza modificare la libreria. Questo è veramente utile.
Il rovescio della medaglia è che la fusione può avvenire per caso. Se due file in un progetto dichiarano un’interfaccia con lo stesso nome nello stesso ambito, si combinano silenziosamente, il che può creare confusione. Un alias di tipo rende invece la collisione un errore.
estensioni vs intersezioni
Entrambi ti consentono di basarti su un tipo esistente:
interface Admin extends User {
permissions: string[];
}
type AdminAlias = UserAlias & { permissions: string[] };
Per le proprietà compatibili, il risultato è lo stesso. La differenza si manifesta con un conflitto. Quando un’interfaccia tenta di modificare il tipo di una proprietà in modo incompatibile, il compilatore lo segnala immediatamente:
interface Base { id: number }
interface Broken extends Base { id: string }
// error TS2430: Interface 'Broken' incorrectly extends interface 'Base'.
// Types of property 'id' are incompatible.
// Type 'string' is not assignable to type 'number'.
Un incrocio non si lamenta quando viene dichiarato. La proprietà in conflitto diventa invece never, un tipo che nessun valore può avere. L’errore appare solo successivamente, quando provi ad usarlo:
type Mixed = { id: number } & { id: string };
const m: Mixed = { id: 1 };
// error TS2322: Type 'number' is not assignable to type 'never'.
Quest’ultimo errore crea più confusione, poiché allontana dall’errore reale. Questo è un motivo pratico per preferire extends quando si creano tipi di oggetto.
Classi e implementa
Una classe può promettere di abbinare una forma utilizzando implements, e questo funziona con un’interfaccia o un alias di tipo oggetto:
class Account implements User {
constructor(public id: number, public name: string) {}
}
Questo compilato senza errori. Nota che implements controlla solo che la classe corrisponda. Non aggiunge alcuna proprietà o codice. Una classe non può implementare un tipo di unione, poiché non esiste un’unica forma da abbinare.
Tipi di sola lettura e di utilità
I tipi di utilità come Readonly, Partial, Pick e Omit funzionano sia con le interfacce che con gli alias. Il risultato è un nuovo tipo, che in genere viene denominato con un alias di tipo:
const r: ReadonlyUser = { id: 3, name: "Dev" };
r.name = "Chitra";
// error TS2540: Cannot assign to 'name' because it is a read-only property.
Ricordare che readonly è solo un controllo in fase di compilazione. In fase di esecuzione l’oggetto è un normale JavaScript e può ancora essere modificato dal codice che ignora i tipi.
Suggerimento: I tipi scompaiono quando TypeScript viene compilato in JavaScript. I dati che arrivano dall’esterno, ad esempio una risposta API, non vengono controllati in fase di esecuzione solo perché hanno un tipo. Convalidarlo, ad esempio con una libreria di schemi. Il nostro tutorial sul recupero dell’API illustra la gestione sicura delle risposte API.
Prestazioni del compilatore
Le indicazioni sulle prestazioni del team TypeScript suggeriscono di preferire le interfacce che utilizzano extends rispetto a intersezioni di grandi dimensioni, poiché il compilatore può memorizzare nella cache le relazioni delle interfacce in modo più efficiente. Nei progetti piccoli e medi difficilmente noterai alcuna differenza. Potrebbe avere importanza in basi di codice molto grandi con tipi profondamente composti. Non lo abbiamo misurato noi stessi.
Quale dovresti usare?
Una regola ampiamente utilizzata, in linea con le indicazioni del manuale TypeScript, è:
- Utilizzare
interfaceper le forme degli oggetti, in particolare le API pubbliche e tutto ciò che le classi implementano o altri possono estendere. - Utilizzare
typeper unioni, tuple, tipi di funzione e tipi mappati o calcolati.
| Stai definendo | Utilizzare |
|---|---|
| Un modello di dati, ad esempio Utente o Ordine | interfaccia |
| Un insieme di valori consentiti | type unione |
| Una coppia o tripla di lunghezza fissa | type tupla |
| Una firma di richiamata | tipo |
| Estendere i tipi di una libreria | interface, attraverso la fusione |
| Una variazione di un tipo esistente | type con un tipo di utilità |
Alcuni team utilizzano semplicemente type ovunque e anche questo funziona bene. Ciò che conta di più è la coerenza, quindi scegli una convenzione, scrivila nella tua guida di stile e lascia che un linter la imponga. La regola consistent-type-definitions di ESLint da typescript-eslint può farlo.
Errori comuni
- Dibattito senza fine. Entrambi vanno bene per gli oggetti, quindi stabilisci la regola una volta e vai avanti.
- Unione accidentale. Il riutilizzo di un nome di interfaccia nello stesso ambito li combina silenziosamente.
- Intersezioni in conflitto. Successivamente creano
nevertipi con errori confusi. - Tipi attendibili in fase di esecuzione. Vengono cancellati, quindi convalida i dati esterni.
- Utilizzare
anyper silenziare gli errori. Disattiva il controllo. Preferisciunknowne restringilo.
Passare da uno all’altro in una codebase esistente
Cambiare un interface in un type, o il contrario, è solitamente sicuro per le forme di oggetti semplici, perché TypeScript confronta i tipi in base alla loro struttura, non in base ai loro nomi. È necessario verificare ancora alcune cose prima di effettuare il passaggio:
- La conversione di un’interfaccia in un alias di tipo rimuove l’unione delle dichiarazioni, quindi qualsiasi seconda dichiarazione con lo stesso nome diventa un errore di identificatore duplicato.
- La conversione di un alias di tipo in un’interfaccia funziona solo per le forme degli oggetti. Unioni, tuple e tipi mappati devono rimanere come alias di tipo.
- I messaggi di errore e i passaggi del mouse sull’editor possono visualizzare il tipo in modo diverso, il che può sorprendere i compagni di squadra ma non cambia il comportamento.
Apporta la modifica in una richiesta pull mirata, esegui il compilatore in modalità rigorosa e la tua suite di test ed evita di mescolarlo con il lavoro sulle funzionalità in modo che i revisori possano vedere che nient’altro è cambiato.
Considerazioni finali
La domanda tra interfaccia e tipo ha una risposta calma: si sovrappongono per gli oggetti, ognuno ha alcune abilità uniche e la coerenza conta più della scelta. Utilizza le interfacce per forme e tipi per tutto il resto, a meno che il tuo team non preferisca diversamente. Per vedere tu stesso le differenze, copia gli esempi sopra in un file ed esegui tsc --noEmit --strict su di esso.
Domande frequenti
Qual è la differenza tra interfaccia e tipo in TypeScript?
Entrambi possono descrivere le forme degli oggetti. Solo il tipo può nominare unioni, tuple e tipi calcolati, mentre solo l’interfaccia supporta l’unione delle dichiarazioni.
Dovrei usare l’interfaccia o digitare per gli oggetti di scena React?
Entrambi funzionano bene. Molti team utilizzano le interfacce per gli oggetti di scena, mentre altri preferiscono il tipo per coerenza con i sindacati. Scegline uno e applicalo a tutto il progetto.
Un tipo può estendere un’interfaccia?
Sì, utilizzando un’intersezione come type Admin = User & { permessi: string[] }. Un’interfaccia può anche estendere un alias di tipo oggetto.
L’interfaccia è più veloce del testo?
La guida alle prestazioni di TypeScript favorisce l’estensione dell’interfaccia su grandi intersezioni in grandi basi di codice. Nei progetti tipici, è improbabile che la differenza sia evidente.
Cos’è la fusione delle dichiarazioni?
Si verifica quando due interfacce con lo stesso nome nello stesso ambito si combinano in una sola. Gli alias di tipo non possono essere uniti e i nomi duplicati causano un errore.
Continua a imparare
Hai bisogno di aiuto con test, WordPress o SEO?
Aiutiamo i team a fornire software migliore e a far crescere i loro siti. Raccontaci a cosa stai lavorando e ti suggeriremo chiaramente il prossimo passo.
Altre guide su test, intelligenza artificiale e sviluppo moderno
Ti è piaciuto questo articolo? Scegli un argomento e passa direttamente ai nostri tutorial più recenti.
Spiegazione delle allucinazioni dell'intelligenza artificiale: perché i modelli linguistici inventano le cose e come ridurle
Spiegazione delle allucinazioni dell'intelligenza artificiale: cosa sono, perché i modelli linguistici le producono, tipi comuni, come ridurle con b…
Leggi l'articolo →DevOpsSpiegazione delle variabili d'ambiente: file .env, segreti e configurazione eseguiti correttamente
Variabili d'ambiente spiegate con esempi Python che abbiamo eseguito: lettura e impostazione, perché ogni valore è una stringa, file .env, ereditari…
Leggi l'articolo →Sviluppo softwareSpiegazione della limitazione della velocità API: algoritmi, intestazioni e gestione degli errori 429
La limitazione della velocità dell'API spiegata con una simulazione che abbiamo eseguito: bucket di token, finestra fissa, finestra scorrevole, risp…
Leggi l'articolo →Automazione dei testDerisione in Python: unittest.mock, patch e gli errori che fanno mentire i test
Mocking in Python con unittest.mock, testato: Mock e return_value, side_effect, patch dove viene utilizzato un nome, specifiche e autospec, date moc…
Leggi l'articolo →Test del softwareSpiegazione della copertura del codice: copertura di linea o di filiale e cosa significa realmente il 100%.
Copertura del codice spiegata con una vera esecuzione di cover.py: copertura di linea, ramo e condizione, lettura di un rapporto, perché la copertur…
Leggi l'articolo →Database e SQLDELETE vs TRUNCATE vs DROP in SQL: differenze, rollback e quando utilizzarli
DELETE vs TRUNCATE vs DROP spiegato con SQL testato: cosa rimuove, rollback, trigger, reimpostazione dell'identità, velocità e come differiscono Pos…
Leggi l'articolo →Database e SQLSpiegazione delle visualizzazioni SQL: crea, utilizza ed evita le insidie, con esempi
Viste SQL spiegate con le query che abbiamo eseguito: creazione di una vista, risultati in tempo reale, nascondere colonne, prestazioni, viste aggio…
Leggi l'articolo →Sviluppo softwareInterfaccia TypeScript e tipo: differenze reali, testate con il compilatore
Interfaccia TypeScript vs tipo, verificata con il vero compilatore: cosa possono fare entrambi, cosa può fare solo uno, fusione delle dichiarazioni,…
Leggi l'articolo →Sviluppo softwareSpiegazione di Python *args e **kwargs: funzioni flessibili con esempi chiari
Python *args e **kwargs spiegati con il codice che abbiamo eseguito: compressione e decompressione, ordine dei parametri, argomenti solo per parole …
Leggi l'articolo →Sviluppo softwareFunzioni Lambda Python: sintassi, usi reali e quando non usarli
Funzioni lambda Python spiegate con il codice che abbiamo eseguito: sintassi, ordinamento con chiave, mappa e filtro, chiusure, trap dell'associazio…
Leggi l'articolo →Intelligenza artificialeCosa sono gli agenti AI? Come funzionano, con un semplice ciclo di agenti che puoi eseguire
Cosa sono gli agenti IA? Una semplice guida al ciclo dell'agente, agli strumenti, alla memoria e ai guardrail, con un piccolo ciclo Python che abbia…
Leggi l'articolo →DevOpsMonitoraggio vs osservabilità: la vera differenza, con log, metriche e tracce
Spiegazione del monitoraggio e dell'osservabilità: errori noti e sconosciuti, log, metriche e tracce, una procedura dettagliata sugli incidenti di p…
Leggi l'articolo →DevOpsVolumi Docker e montaggi di associazione: mantenere i dati del contenitore nel modo giusto
Volumi Docker e montaggi di collegamento spiegati: perché i dati del contenitore scompaiono, volumi denominati, montaggi di collegamento, tmpfs, ese…
Leggi l'articolo →Automazione dei testTDD vs BDD: differenze chiave, una caratteristica costruita in entrambi i modi e quando utilizzarli ciascuno
TDD vs BDD spiegato con una funzione di sconto costruita in entrambi i modi: refactoring rosso-verde con il codice che abbiamo eseguito, scenari "Da…
Leggi l'articolo →Sviluppo softwareGit merge vs rebase: differenze, esempi e quando usare ciascuno
Git merge vs rebase spiegato con output reale del log, una tabella di confronto, la regola d'oro del rebase e un flusso di lavoro sicuro per i team.
Leggi l'articolo →Sviluppo softwareList comprehension in Python: sintassi, esempi e quando evitarla
List comprehension in Python spiegata: sintassi, filtri, if-else, cicli annidati, tempi misurati e i casi in cui un normale ciclo for è meglio.
Leggi l'articolo →Database e SQLSQL JOIN spiegati con esempi (Inner, Left, Right, Full)
Capisci i JOIN SQL passo dopo passo con tabelle e query di esempio: INNER, LEFT, RIGHT, FULL, CROSS e SELF, con errori comuni e consigli sulle prest…
Leggi l'articolo →