interface Child extends Parent crée une nouvelle interface qui possède tous les membres de Parent, plus les siens. Les valeurs du type enfant peuvent être utilisées partout où le parent est attendu, puisqu'elles ont toutes ses propriétés.
Le même mot-clé apparaît à quatre endroits en TypeScript, avec des sens voisins mais différents :
| Où | Exemple | Signification |
|---|---|---|
| Interface | interface Dog extends Animal {} | hériter des membres du parent |
| Classe | class Dog extends Animal {} | hériter des champs et des implémentations de méthodes (JavaScript à l'exécution) |
| Paramètre générique | <T extends Animal> | contrainte : l'argument de type doit être assignable à Animal |
| Type conditionnel | T extends string ? A : B | test : T est-il assignable à string ? |
Étendre plusieurs interfaces
Listez plusieurs parents après extends, séparés par des virgules. Le résultat possède les membres de tous :
interface Identified {
id: number;
}
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
interface Post extends Identified, Timestamped {
title: string;
}
// Post requires id, createdAt, updatedAt and title
Si deux parents déclarent la même propriété avec des types qui ne sont pas identiques, l'enfant provoque l'erreur TS2320 (Interface 'Post' cannot simultaneously extend types 'A' and 'B'), sauf si l'enfant redéclare cette propriété avec un type assignable aux deux.
Étendre un alias de type
Un alias type n'a pas de clause extends. Vous le complétez avec une intersection, &, qui combine les membres des deux côtés. Une interface peut aussi étendre directement un alias de type objet, et un alias de type peut faire l'intersection avec une interface : les deux sortes se mélangent librement.
Les deux approches se comportent de la même façon jusqu'à ce que des propriétés entrent en conflit : extends signale une propriété contradictoire au niveau de la déclaration, alors que & la transforme en silence en never. La comparaison se trouve sur la page interface ou type, et les détails de & sur la page des types intersection.
Redéfinir le type d'une propriété
Une interface enfant peut redéclarer une propriété du parent, mais seulement avec un type assignable à celui du parent. Affiner string en un littéral, ou retirer null d'une union, est autorisé. Changer le type en quelque chose sans rapport, non :
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'.
Cette règle existe pour qu'un Label puisse toujours être utilisé partout où un Shape est attendu. Si kind pouvait être un nombre, du code écrit pour Shape qui appelle shape.kind.toUpperCase() casserait.
Remplacer des propriétés avec Omit
Quand vous avez vraiment besoin d'un autre type pour une propriété héritée, retirez-la d'abord du parent avec Omit, puis déclarez la nouvelle. Un cas courant est la conversion de données d'API, où les identifiants et les dates arrivent sous forme de chaînes :
Omit<ApiUser, "id" | "createdAt"> vaut { name: string }, donc les nouveaux id et createdAt n'entrent en conflit avec rien. Pick, Partial et les autres types utilitaires s'étendent de la même façon.
extends avec les classes
class Dog extends Animal est l'héritage de classes de JavaScript et, contrairement au extends des interfaces, il existe à l'exécution : Dog reçoit le constructeur, les champs et les implémentations de méthodes d'Animal, et new Dog(...) instanceof Animal vaut true. TypeScript ajoute des vérifications de types par-dessus, comme le mot-clé override :
Une classe ne peut étendre (extends) qu'une seule classe, mais elle peut implémenter (implements) autant d'interfaces qu'elle veut. implements ne copie rien : il vérifie seulement que la classe déclare ce qu'exige l'interface. super, les membres protected et override sont présentés dans héritage.
extends dans les génériques et les types conditionnels
Entre chevrons, extends est une contrainte. <T extends { length: number }> accepte tout argument de type qui possède une length numérique, et permet au corps de la fonction d'utiliser .length. Dans un type conditionnel, T extends U ? X : Y demande si T est assignable à U.
Dans les deux cas, extends signifie « est assignable à », la même relation que vérifie la forme interface. Les contraintes sont traitées en détail dans contraintes génériques, et les tests au niveau des types dans types conditionnels.
Questions fréquentes
Comment étendre une interface en TypeScript ?
Écrivez interface Child extends Parent { ... }. L'enfant possède toutes les propriétés du parent, plus celles qu'il déclare. Pour étendre plusieurs interfaces d'un coup, séparez-les par des virgules : interface C extends A, B { ... }.
Comment étendre un alias de type en TypeScript ?
Un alias de type ne peut pas utiliser extends ; combinez plutôt les types avec une intersection : type Admin = User & { permissions: string[] }. Une interface peut aussi étendre directement un alias de type objet : interface Admin extends User { permissions: string[] }.
Peut-on redéfinir le type d'une propriété en étendant une interface ?
Seulement avec un type compatible, plus étroit : kind: "circle" peut remplacer kind: string. Un type différent, comme number pour une propriété string, provoque l'erreur TS2430 (Interface 'X' incorrectly extends interface 'Y'). Pour remplacer une propriété par un type sans rapport, retirez-la d'abord : interface User extends Omit<ApiUser, "id"> { id: number }.
Quelle est la différence entre extends et implements ?
extends hérite : une interface reçoit les membres du parent, et une classe reçoit les champs et les implémentations de méthodes de la classe parente. implements ne fait que vérifier : une classe qui implémente une interface doit déclarer elle-même chaque membre, et ne gagne rien de l'interface.
Que signifie extends dans un générique comme <T extends string> ?
C'est une contrainte, pas un héritage. T extends string signifie que l'argument de type doit être assignable à string, vous pouvez donc utiliser les méthodes de chaîne sur T dans la fonction. Dans un type conditionnel, T extends U ? X : Y est un test : si T est assignable à U, le résultat est X, sinon Y.