interface Child extends Parent crea una nuova interfaccia con tutti i membri di Parent più i suoi. I valori del tipo figlio si possono usare ovunque sia atteso il genitore, perché hanno tutte le sue proprietà.
La stessa parola chiave compare in quattro punti di TypeScript, con significati collegati ma diversi:
| Dove | Esempio | Significato |
|---|---|---|
| Interfaccia | interface Dog extends Animal {} | ereditare i membri del genitore |
| Classe | class Dog extends Animal {} | ereditare campi e implementazioni dei metodi (JavaScript a runtime) |
| Parametro generico | <T extends Animal> | vincolo: l'argomento di tipo deve essere assegnabile ad Animal |
| Conditional type | T extends string ? A : B | test: T è assegnabile a string? |
Estendere più interfacce
Elenca più genitori dopo extends, separati da virgole. Il risultato ha i membri di tutti:
interface Identified {
id: number;
}
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
interface Post extends Identified, Timestamped {
title: string;
}
// Post requires id, createdAt, updatedAt and title
Se due genitori dichiarano la stessa proprietà con tipi non identici, la figlia dà l'errore TS2320 (Interface 'Post' cannot simultaneously extend types 'A' and 'B'), a meno che la figlia non dichiari di nuovo quella proprietà con un tipo assegnabile a entrambi.
Estendere un alias di tipo
Un alias type non ha la clausola extends. Ci costruisci sopra con un'intersezione, &, che combina i membri dei due lati. Un'interfaccia può anche estendere direttamente un alias di tipo oggetto, e un alias di tipo può intersecarsi con un'interfaccia: i due costrutti si mescolano senza problemi.
Le due strade si comportano allo stesso modo finché le proprietà non vanno in conflitto: extends segnala la proprietà in conflitto nella dichiarazione, mentre & la trasforma in never senza avvisare. Il confronto è nella pagina su interface e type, e i dettagli di & sono nella pagina sui tipi intersezione.
Ridefinire i tipi delle proprietà
Un'interfaccia figlia può dichiarare di nuovo una proprietà del genitore, ma solo con un tipo assegnabile a quello del genitore. Restringere string a un letterale, o togliere null da un'unione, è consentito. Cambiare il tipo in qualcosa di non correlato no:
index.ts(13,11): error TS2430: Interface 'Label' incorrectly extends interface 'Shape'.
Types of property 'kind' are incompatible.
Type 'number' is not assignable to type 'string'.
La regola esiste perché un Label si possa ancora usare ovunque sia atteso uno Shape. Se kind potesse essere un numero, il codice scritto per Shape che chiama shape.kind.toUpperCase() si romperebbe.
Sostituire proprietà con Omit
Quando ti serve davvero un tipo diverso per una proprietà ereditata, rimuovila prima dal genitore con Omit, poi dichiara quella nuova. Un caso comune è la conversione dei dati di un'API, dove id e date arrivano come stringhe:
Omit<ApiUser, "id" | "createdAt"> è { name: string }, quindi i nuovi id e createdAt non vanno in conflitto con nulla. Pick, Partial e gli altri utility types si possono estendere allo stesso modo.
extends con le classi
class Dog extends Animal è l'ereditarietà delle classi di JavaScript e, a differenza dell'extends delle interfacce, esiste a runtime: Dog riceve il costruttore, i campi e le implementazioni dei metodi di Animal, e new Dog(...) instanceof Animal è true. TypeScript aggiunge sopra dei controlli di tipo, come la parola chiave override:
Una classe può fare extends di una sola classe, ma può fare implements di un numero qualsiasi di interfacce. implements non copia nulla: controlla solo che la classe dichiari ciò che l'interfaccia richiede. super, i membri protetti e override sono spiegati in ereditarietà.
extends nei generics e nei conditional types
Dentro le parentesi angolari, extends è un vincolo. <T extends { length: number }> accetta qualsiasi argomento di tipo che abbia un length numerico e permette al corpo della funzione di usare .length. In un conditional type, T extends U ? X : Y chiede se T è assegnabile a U.
In entrambi i casi extends significa "è assegnabile a", la stessa relazione che controlla la forma delle interfacce. I vincoli sono spiegati a fondo in vincoli generici, e i test a livello di tipo in conditional types.
Domande frequenti
Come si estende un'interfaccia in TypeScript?
Scrivi interface Child extends Parent { ... }. La figlia ha tutte le proprietà del genitore più quelle che dichiara lei. Per estendere più interfacce insieme, separale con virgole: interface C extends A, B { ... }.
Come si estende un alias di tipo in TypeScript?
Un alias di tipo non può usare extends; combina i tipi con un'intersezione: type Admin = User & { permissions: string[] }. Anche un'interfaccia può estendere direttamente un alias di tipo oggetto: interface Admin extends User { permissions: string[] }.
Posso ridefinire il tipo di una proprietà quando estendo un'interfaccia?
Solo con un tipo compatibile e più stretto: kind: "circle" può sostituire kind: string. Un tipo diverso, come number per una proprietà string, dà l'errore TS2430 (Interface 'X' incorrectly extends interface 'Y'). Per sostituire una proprietà con un tipo non correlato, rimuovila prima: interface User extends Omit<ApiUser, "id"> { id: number }.
Che differenza c'è tra extends e implements?
extends eredita: un'interfaccia riceve i membri del genitore e una classe riceve i campi e le implementazioni dei metodi della classe genitore. implements si limita a controllare: una classe che implementa un'interfaccia deve dichiarare da sé ogni membro e non riceve nulla dall'interfaccia.
Cosa significa extends in un generic come <T extends string>?
È un vincolo, non ereditarietà. T extends string significa che l'argomento di tipo deve essere assegnabile a string, quindi dentro la funzione puoi usare i metodi delle stringhe su T. In un conditional type, T extends U ? X : Y è un test: se T è assegnabile a U, il risultato è X, altrimenti Y.