Pompei Tech
Guide · typescript

Interfacce e type alias

Interfacce e type alias

Pensa a un contratto di affitto standard. Invece di riscrivere da zero le stesse clausole ogni volta che affitti un appartamento diverso, usi un modello: cambi il nome dell'inquilino, l'indirizzo, l'importo del canone, ma la struttura del contratto — quali clausole ci sono, cosa deve contenere ogni sezione — resta la stessa, definita in un unico posto. Se un giorno la legge cambia e devi aggiungere una clausola obbligatoria, la aggiorni nel modello, non in ogni singolo contratto firmato.

TypeScript offre due meccanismi per fare esattamente questo con i tipi: dare loro un nome centrale e riutilizzabile. Si chiamano interfacce e type alias. Li studieremo entrambi in profondità, e vedremo quando ha senso usare l'uno o l'altro.

Type alias

Ripensa alla sintassi : {valuta: string, valore: number} che abbiamo usato finora per le annotazioni di tipo. Questa sintassi diventa sempre più complicata man mano che aggiungiamo proprietà al tipo. Inoltre, se passiamo oggetti di questo tipo attraverso varie funzioni e variabili, finiamo per avere un sacco di tipi che devono essere aggiornati manualmente ogni volta che serve fare una modifica!

I type alias aiutano a risolvere questo problema, permettendoci di:

  • Definire un nome più significativo per questo tipo.
  • Dichiarare la forma del tipo in un unico posto.
  • Importare ed esportare questo tipo tra i moduli, esattamente come se fosse un valore importabile/esportabile.
ts
// @filename: tipi.ts
export type 
type Importo = {
    valuta: string;
    valore: number;
}
Importo
= {
valuta: stringvaluta: string; valore: numbervalore: number; }; // @filename: utilities.ts import {
type Importo = {
    valuta: string;
    valore: number;
}
Importo
} from "./tipi";
function function stampaImporto(imp: Importo): voidstampaImporto(imp: Importoimp:
type Importo = {
    valuta: string;
    valore: number;
}
Importo
) {
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(
imp: Importo
imp
);
const { const valuta: stringvaluta, const valore: numbervalore } = imp: Importoimp; var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(`${const valuta: stringvaluta} ${const valore: numbervalore}`);
}

Un paio di cose da notare:

  • Il tooltip su imp, nel tuo editor, ora è molto più pulito e più semantico (significativo, legato al concetto che rappresenta) — dice Importo invece di ripetere tutta la struttura ogni volta.
  • Import/export di questo type funziona esattamente come farebbe per una funzione o una classe in JavaScript.

È importante capire che il nome Importo è solo per nostra comodità. Questo resta comunque un sistema di tipi strutturale, come abbiamo visto nel capitolo precedente:

ts
// @filename: tipi.ts
export type 
type Importo = {
    valuta: string;
    valore: number;
}
Importo
= {
valuta: stringvaluta: string; valore: numbervalore: number; }; // @filename: utilities.ts import {
type Importo = {
    valuta: string;
    valore: number;
}
Importo
} from "./tipi";
function function stampaImporto(imp: Importo): voidstampaImporto(imp: Importoimp:
type Importo = {
    valuta: string;
    valore: number;
}
Importo
) {
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(imp: Importoimp);
} const
const donazione: {
    valuta: string;
    valore: number;
    descrizione: string;
}
donazione
= {
valuta: stringvaluta: "EUR", valore: numbervalore: 30.0, descrizione: stringdescrizione: "Donazione al banco alimentare", }; // Funziona: la "forma" corrisponde, anche se donazione ha una // proprietà extra e non è mai stata dichiarata esplicitamente come "Importo" function stampaImporto(imp: Importo): voidstampaImporto(
const donazione: {
    valuta: string;
    valore: number;
    descrizione: string;
}
donazione
);

Diamo un'occhiata per un momento alla sintassi di dichiarazione:

ts
type Importo = {
  valuta: string;
  valore: number;
};

Un paio di cose da segnalare:

  1. Questa è una delle rare occasioni in cui vediamo informazioni di tipo alla destra dell'operatore di assegnazione (=).
  2. Stiamo usando il TitleCase (iniziale maiuscola) per formattare il nome dell'alias. Questa è una convenzione comune.
  3. Come vediamo qui sotto, possiamo dichiarare un alias con un dato nome solo una volta in un determinato scope. È un po' come funziona la dichiarazione di una variabile let o const:
ts
type Importo = {
Duplicate identifier 'Importo'.
valuta: stringvaluta: string; valore: numbervalore: number; }; type Importo = {
Duplicate identifier 'Importo'.
fallira: "questo non funzionerà"fallira: "questo non funzionerà"; };

Un type alias può contenere qualsiasi tipo, dato che è letteralmente un alias (un nome) per un tipo di qualsiasi natura.

Ecco un esempio di come possiamo applicare tipi di ritorno espliciti alle funzioni, e poi "ripulire" un po' di codice dal capitolo precedente sugli union e intersection type, usando i type alias:

ts
// @filename: comune.ts
export function function lanciaMoneta(): "testa" | "croce"lanciaMoneta() {
  if (var Math: Math
An intrinsic object that provides basic mathematics functionality and constants.
Math
.Math.random(): number
Returns a pseudorandom number between 0 and 1.
random
() > 0.5) return "testa";
return "croce"; } export const
const successo: readonly ["successo", {
    readonly nome: "Davide";
    readonly email: "davide@example.com";
}]
successo
= ["successo", { nome: "Davide"nome: "Davide", email: "davide@example.com"email: "davide@example.com" }] as
type const = readonly ["successo", {
    readonly nome: "Davide";
    readonly email: "davide@example.com";
}]
const
;
export const const errore: readonly ["errore", Error]errore = ["errore", new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
("Qualcosa è andato storto!")] as type const = readonly ["errore", Error]const;
// @filename: originale.ts import { function lanciaMoneta(): "testa" | "croce"lanciaMoneta,
const successo: readonly ["successo", {
    readonly nome: "Davide";
    readonly email: "davide@example.com";
}]
successo
, const errore: readonly ["errore", Error]errore } from "./comune";
/** * Versione ORIGINALE */ export function
function forseRecuperaDatiUtente(): readonly ["errore", Error] | readonly ["successo", {
    readonly nome: string;
    readonly email: string;
}]
Versione ORIGINALE
forseRecuperaDatiUtente
():
| readonly ["errore", Error] | readonly ["successo", { readonly nome: stringnome: string; readonly email: stringemail: string }] { // implementazione identica in entrambi gli esempi if (function lanciaMoneta(): "testa" | "croce"lanciaMoneta() === "testa") { return
const successo: readonly ["successo", {
    readonly nome: "Davide";
    readonly email: "davide@example.com";
}]
successo
;
} else { return const errore: readonly ["errore", Error]errore; } } // @filename: con-alias.ts import { function lanciaMoneta(): "testa" | "croce"lanciaMoneta,
const successo: readonly ["successo", {
    readonly nome: "Davide";
    readonly email: "davide@example.com";
}]
successo
, const errore: readonly ["errore", Error]errore } from "./comune";
type type EsitoErrore = readonly ["errore", Error]EsitoErrore = readonly ["errore", Error]; type
type EsitoSuccesso = readonly ["successo", {
    readonly nome: string;
    readonly email: string;
}]
EsitoSuccesso
= readonly [
"successo", { readonly nome: stringnome: string; readonly email: stringemail: string }, ]; type type Esito = EsitoErrore | EsitoSuccessoEsito = type EsitoErrore = readonly ["errore", Error]EsitoErrore |
type EsitoSuccesso = readonly ["successo", {
    readonly nome: string;
    readonly email: string;
}]
EsitoSuccesso
;
/** * Versione RIPULITA */ export function function forseRecuperaDatiUtente2(): Esito
Versione RIPULITA
forseRecuperaDatiUtente2
(): type Esito = EsitoErrore | EsitoSuccessoEsito {
// implementazione identica in entrambi gli esempi if (function lanciaMoneta(): "testa" | "croce"lanciaMoneta() === "testa") { return
const successo: readonly ["successo", {
    readonly nome: "Davide";
    readonly email: "davide@example.com";
}]
successo
;
} else { return const errore: readonly ["errore", Error]errore; } }

Ereditarietà nei type alias

Puoi creare type alias che combinano tipi esistenti con nuovo comportamento, usando gli intersection type (&):

ts
type 
type DataSpeciale = Date & {
    getDescrizione(): string;
}
DataSpeciale
= Date & { function getDescrizione(): stringgetDescrizione(): string };
const const capodanno: DataSpecialecapodanno:
type DataSpeciale = Date & {
    getDescrizione(): string;
}
DataSpeciale
= var Object: ObjectConstructor
Provides functionality common to all JavaScript objects.
Object
.
ObjectConstructor.assign<Date, {
    getDescrizione: () => "Ultimo giorno dell'anno";
}>(target: Date, source: {
    getDescrizione: () => "Ultimo giorno dell'anno";
}): Date & {
    getDescrizione: () => "Ultimo giorno dell'anno";
} (+3 overloads)
Copy the values of all of the enumerable own properties from one or more source objects to a target object. Returns the target object.
@paramtarget The target object to copy to.@paramsource The source object from which to copy properties.
assign
(new
var Date: DateConstructor
new () => Date (+4 overloads)
Date
(), {
getDescrizione: () => "Ultimo giorno dell'anno"getDescrizione: () => "Ultimo giorno dell'anno", });
const capodanno: DataSpeciale
capodanno
.function getDescrizione(): stringgetDescrizione();

Anche se non esiste una vera keyword extends utilizzabile quando si definiscono type alias, un intersection type ha un effetto molto simile.

Interfacce

Un'interfaccia è un modo per definire un tipo oggetto (da non confondere con un tipo chiamato object, di cui parleremo più avanti). In questo contesto, pensa a un tipo oggetto come a qualcosa che assomiglia a questo:

ts
{
  campo: "valore";
}

I tipi oggetto possono essere anonimi:

ts
function stampaImporto(imp: { valuta: string; valore: number }) {}

oppure descritti usando un type alias, come abbiamo appena visto:

ts
type Importo = {
  valuta: string;
  valore: number;
};

string | number è un esempio di qualcosa che non può essere descritto usando un tipo oggetto, perché fa uso dell'operatore union.

Come i type alias, le interfacce possono essere importate/esportate tra moduli proprio come i valori, e servono a dare un "nome" a un tipo specifico.

ts
interface Importo {
  valuta: string;
  valore: number;
}

function stampaImporti(imp: Importo) {
  imp.valuta; // ✅ autocompletamento disponibile qui
}

Ereditarietà nelle interfacce

extends

Se hai mai visto una classe JavaScript che "eredita" comportamento da una classe base, hai visto un esempio di quella che TypeScript chiama una heritage clause (clausola di ereditarietà): extends.

js
function consumaCibo(arg) {}

class AnimaleCheMangia {
  mangia(cibo) {
    consumaCibo(cibo);
  }
}
class Gatto extends AnimaleCheMangia {
  miagola() {
    return "miao";
  }
}

const g = new Gatto();
g.mangia; // ereditato da AnimaleCheMangia
g.miagola(); // "miao"
  • Esattamente come in JavaScript, una sottoclasse fa extends da una classe base.
  • In aggiunta, una "sotto-interfaccia" fa extends da un'interfaccia base, come mostrato nell'esempio qui sotto:
ts
interface Animale {
  Animale.isAlive(): booleanisAlive(): boolean;
}
interface Mammifero extends Animale {
  Mammifero.getColorePelo(): stringgetColorePelo(): string;
}
interface Criceto extends Mammifero {
  Criceto.squittisce(): stringsquittisce(): string;
}
function function accudisciCriceto(c: Criceto): voidaccudisciCriceto(c: Cricetoc: Criceto) {
  c: Cricetoc.Mammifero.getColorePelo(): stringgetColorePelo(); // ereditato da Mammifero
  c: Cricetoc.Criceto.squittisce(): stringsquittisce(); // definito su Criceto
  c: Cricetoc.Animale.isAlive(): booleanisAlive(); // ereditato da Animale, tramite Mammifero
}

implements

TypeScript aggiunge una seconda heritage clause che può essere usata per dichiarare che una data classe deve produrre istanze conformi a una data interfaccia: implements.

ts
function function consumaCibo(arg: unknown): voidconsumaCibo(arg: unknownarg: unknown) {}

interface AnimaleGenerico {
  AnimaleGenerico.mangia(cibo: unknown): voidmangia(cibo: unknowncibo: unknown): void;
}

class Cane implements AnimaleGenerico {
Class 'Cane' incorrectly implements interface 'AnimaleGenerico'. Property 'mangia' is missing in type 'Cane' but required in type 'AnimaleGenerico'.
Cane.abbaia(): stringabbaia() { return "bau"; } }

Nell'esempio sopra, vediamo che TypeScript protesta perché non abbiamo aggiunto un metodo mangia() alla nostra classe Cane. Senza questo metodo, le istanze di Cane non sono conformi all'interfaccia AnimaleGenerico. Aggiorniamo il codice:

ts
function function consumaCibo(arg: unknown): voidconsumaCibo(arg: unknownarg: unknown) {}

interface AnimaleGenerico {
  AnimaleGenerico.mangia(cibo: unknown): voidmangia(cibo: unknowncibo: unknown): void;
}

class class CaneCane implements AnimaleGenerico {
  Cane.abbaia(): stringabbaia() {
    return "bau";
  }
  Cane.mangia(cibo: unknown): voidmangia(cibo: unknowncibo: unknown) {
    function consumaCibo(arg: unknown): voidconsumaCibo(cibo: unknowncibo);
  }
}

Ecco, ora va meglio. Anche se TypeScript (e JavaScript) non supportano una vera ereditarietà multipla (estendere più di una classe base), questa keyword implements ci dà la capacità di validare, a compile time, che le istanze di una classe siano conformi a uno o più "contratti" (tipi). Nota che extends e implements possono essere usati insieme:

ts
function function consumaCibo(arg: unknown): voidconsumaCibo(arg: unknownarg: unknown) {}

class class OrganismoViventeOrganismoVivente {
  OrganismoVivente.isAlive(): booleanisAlive() {
    return true;
  }
}
interface AnimaleGenerico {
  AnimaleGenerico.mangia(cibo: unknown): voidmangia(cibo: unknowncibo: unknown): void;
}
interface PuoAbbaiare {
  PuoAbbaiare.abbaia(): stringabbaia(): string;
}

class class Cane2Cane2 extends class OrganismoViventeOrganismoVivente implements AnimaleGenerico, PuoAbbaiare {
  Cane2.abbaia(): stringabbaia() {
    return "bau";
  }
  Cane2.mangia(cibo: unknown): voidmangia(cibo: unknowncibo: unknown) {
    function consumaCibo(arg: unknown): voidconsumaCibo(cibo: unknowncibo);
  }
}

Anche se è possibile usare implements con un type alias...

ts
function function consumaCibo(arg: unknown): voidconsumaCibo(arg: unknownarg: unknown) {}

type 
type PuoSaltare = {
    saltaAdAltezza(): number;
}
PuoSaltare
= {
function saltaAdAltezza(): numbersaltaAdAltezza(): number; }; class class Cane3Cane3 implements
type PuoSaltare = {
    saltaAdAltezza(): number;
}
PuoSaltare
{
Cane3.saltaAdAltezza(): numbersaltaAdAltezza() { return 1.7; } Cane3.mangia(cibo: unknown): voidmangia(cibo: unknowncibo: unknown) { function consumaCibo(arg: unknown): voidconsumaCibo(cibo: unknowncibo); } }

se il tipo dovesse mai violare le "regole del tipo oggetto", ci sono alcuni potenziali problemi...

ts
function function consumaCibo(arg: unknown): voidconsumaCibo(arg: unknownarg: unknown) {}

type 
type PuoSaltare = string | {
    saltaAdAltezza(): number;
}
PuoSaltare
= { function saltaAdAltezza(): numbersaltaAdAltezza(): number } | string;
class class Cane3Cane3 implements PuoSaltare {
A class can only implement an object type or intersection of object types with statically known members.
Cane3.abbaia(): stringabbaia() { return "bau"; } Cane3.mangia(cibo: unknown): voidmangia(cibo: unknowncibo: unknown) { function consumaCibo(arg: unknown): voidconsumaCibo(cibo: unknowncibo); } }

Questi tipi di errori iniziano a comparire nel punto concettualmente equivalente al call site, piuttosto che al declaration site, il che non è l'ideale.

Per questo motivo, è preferibile usare le interfacce per i tipi che vengono usati con la heritage clause implements.

Interfacce "aperte"

Le interfacce di TypeScript sono "aperte", il che significa che, a differenza dei type alias, puoi avere dichiarazioni multiple con lo stesso nome nello stesso scope:

ts
interface AnimaleGenerico {
  // da prima
  AnimaleGenerico.mangia(cibo: unknown): voidmangia(cibo: unknowncibo: unknown): void;
}
function function nutri(animale: AnimaleGenerico): voidnutri(animale: AnimaleGenericoanimale: AnimaleGenerico) {
  animale: AnimaleGenericoanimale.AnimaleGenerico.mangia(cibo: unknown): voidmangia; // disponibile
  animale: AnimaleGenericoanimale.AnimaleGenerico.isAlive(): booleanisAlive; // disponibile grazie alla seconda dichiarazione qui sotto
}

// SECONDA DICHIARAZIONE DELLO STESSO NOME
interface AnimaleGenerico {
  AnimaleGenerico.isAlive(): booleanisAlive(): boolean;
}

Queste dichiarazioni vengono unite (merge) per creare un risultato identico a quello che vedresti se i metodi isAlive e mangia fossero entrambi su un'unica dichiarazione di interfaccia.

Ti starai chiedendo: dove e come è utile questa cosa?

Caso d'uso: estendere tipi nativi o di librerie esistenti

Immagina una situazione in cui vuoi aggiungere una proprietà globale all'oggetto window:

ts
window.document; // una proprietà già esistente
window.exampleProperty = 42;
// ❌ Property 'exampleProperty' does not exist on type 'Window & typeof globalThis'.

Se aggiungiamo una dichiarazione che aumenta l'interfaccia Window esistente, dicendo a TypeScript che quella proprietà esiste, l'errore sparisce:

ts
// diciamo a TS che "exampleProperty" esiste
interface Window {
  exampleProperty: number;
}

window.document; // una proprietà già esistente
window.exampleProperty = 42; // ✅ ora funziona

Quello che abbiamo fatto qui è estendere un'interfaccia Window esistente, che TypeScript ha già configurato per noi dietro le quinte.

Scegliere tra type e interface

In molte situazioni, sia un alias type che un'interface andrebbero benissimo, tuttavia...

  1. Se devi definire qualcosa di diverso da un tipo oggetto (per esempio, usando l'operatore union |), devi usare un type alias.
  2. Se devi definire un tipo da usare con la heritage clause implements su una class, usa un'interfaccia.
  3. Se devi permettere a chi consuma i tuoi tipi di estenderli, devi usare un'interfaccia.

Ricorsione

I tipi ricorsivi sono auto-referenziali, e vengono spesso usati per descrivere tipi annidabili all'infinito. Per esempio, considera array di numeri annidabili all'infinito:

ts
[3, 4, [5, 6, [7], 59], 221];

Potresti leggere o vedere indicazioni secondo cui devi usare una combinazione di interface e type per i tipi ricorsivi. Dalla versione 3.7 di TypeScript, questo è molto più semplice, e funziona sia con i type alias che con le interfacce:

ts
type type NumeriAnnidati = number | NumeriAnnidati[]NumeriAnnidati = number | type NumeriAnnidati = number | NumeriAnnidati[]NumeriAnnidati[];

const const val: NumeriAnnidatival: type NumeriAnnidati = number | NumeriAnnidati[]NumeriAnnidati = [3, 4, [5, 6, [7], 59], 221];

if (typeof const val: NumeriAnnidati[]val !== "number") {
  const val: NumeriAnnidati[]val.Array<NumeriAnnidati>.push(...items: NumeriAnnidati[]): number
Appends new elements to the end of an array, and returns the new length of the array.
@paramitems New elements to add to the array.
push
(41); // OK
const val: NumeriAnnidati[]val.Array<NumeriAnnidati>.push(...items: NumeriAnnidati[]): number
Appends new elements to the end of an array, and returns the new length of the array.
@paramitems New elements to add to the array.
push
("questo non funzionerà");
Argument of type 'string' is not assignable to parameter of type 'NumeriAnnidati'.
}