Une classe TypeScript hérite d'une autre avec extends. La sous-classe reçoit chaque champ et chaque méthode du parent, appelle le constructeur parent avec super(...), et peut redéfinir des méthodes ; TypeScript vérifie que chaque redéfinition reste compatible avec les types du parent.
C'est l'héritage de classes de JavaScript (voir l'héritage en JavaScript pour la chaîne de prototypes qui se trouve derrière). TypeScript ajoute les vérifications de types : un Dog est assignable partout où un Animal est attendu, et le compilateur vérifie la redéfinition et l'appel à super.
Redéfinir des méthodes
Une méthode de sous-classe portant le même nom remplace celle du parent. Son type doit être assignable à celui du parent : paramètres identiques ou compatibles, type de retour identique ou plus étroit. Sinon, tout code qui détient un Animal pourrait appeler la méthode avec des arguments que la sous-classe ne sait pas traiter.
Changer le paramètre en value: string provoquerait l'erreur TS2416 : Property 'format' in type 'CurrencyFormatter' is not assignable to the same property in base type 'Formatter'. La boucle montre pourquoi : elle appelle format avec un nombre sur chaque élément, sans savoir quelle sous-classe elle a.
Il y a une faille. Les paramètres de méthode sont comparés de façon bivariante, si bien qu'une redéfinition qui accepte un type plus étroit, comme format(value: 1 | 2), compile alors que la boucle pourrait toujours lui passer 1234567. Les types de retour sont vérifiés strictement : renvoyer number au lieu de string provoque TS2416.
Le mot-clé override et noImplicitOverride
override signifie « ce membre en remplace un de la classe de base », et le compilateur vous tient à votre parole. Si la base n'a pas ce membre (une faute de frappe, ou une méthode que quelqu'un a renommée dans le parent), c'est une erreur :
index.ts(9,14): error TS4117: This member cannot have an 'override' modifier because it is not declared in the base class 'Animal'. Did you mean 'speak'?
Sans override, cette faute de frappe compile et speak() renvoie en silence "...". Le mot-clé n'aide que là où il est écrit : activez donc noImplicitOverride dans tsconfig.json. Chaque méthode qui redéfinit un membre de la base doit alors le dire, et un simple speak() dans une sous-classe provoque l'erreur TS4114 (This member must have an 'override' modifier because it overrides a member in the base class 'Animal').
{
"compilerOptions": {
"strict": true,
"noImplicitOverride": true
}
}
noImplicitOverride ne fait pas partie de strict ; vous devez l'ajouter vous-même. override est effacé du JavaScript compilé.
Membres protected dans les sous-classes
Les membres private du parent sont invisibles dans la sous-classe. Les membres protected sont visibles, et c'est la raison d'être de protected : c'est l'API qu'une classe parente offre à ses enfants.
Voir modificateurs d'accès pour les règles complètes, y compris pourquoi ni private ni protected ne sont imposés à l'exécution.
Redéclarer un champ avec un type plus étroit
Une sous-classe sait souvent qu'un champ contient quelque chose de plus précis que ce qu'indique le parent. Le redéclarer en pet: Dog a un effet à l'exécution avec les champs de classe modernes : le champ de la sous-classe est initialisé à undefined après le retour de super(), ce qui efface ce que le constructeur parent avait stocké. Sauf si le constructeur de la sous-classe l'assigne de nouveau, TypeScript le signale par TS2612 (Property 'pet' will overwrite the base property in 'Home'). Utilisez declare pour ne changer que le type :
declare n'émet rien : la valeur définie par le constructeur de Home survit donc. C'est le constructeur qui n'accepte qu'un Dog qui rend le type plus étroit exact.
implements ou extends
Les deux mots-clés répondent à des questions différentes, et une classe peut utiliser les deux.
extends | implements | |
|---|---|---|
| Fonctionne avec | une seule classe | autant d'interfaces que voulu (ou d'alias de types objet) |
| Apporte du code | oui : champs, méthodes, constructeur | non, il vérifie seulement la forme |
| Existe à l'exécution | oui, la chaîne de prototypes | non, effacé |
instanceof fonctionne | oui | non |
| Usage typique | partager un vrai comportement | promettre une forme au reste du code |
Une classe ne peut étendre qu'une seule classe (TS1174: Classes can only extend a single class). Pour combiner des comportements venus de plusieurs endroits, implémentez plusieurs interfaces et partagez le code par composition (un champ qui contient un objet auxiliaire) ou par mixins.
Mixins
Un mixin est une fonction qui prend une classe et renvoie une sous-classe de celle-ci. Il offre une forme d'héritage multiple qui passe la vérification des types :
Le any[] de Constructor est obligatoire : TypeScript n'accepte comme base de mixin qu'une classe dont le constructeur prend un unique paramètre rest de type any[]. Les mixins fonctionnent mais rendent les types et les stack traces plus difficiles à lire : essayez d'abord la composition.
Erreurs fréquentes
- Appeler une méthode redéfinissable depuis le constructeur de base. Le constructeur de base s'exécute avant l'initialisation des champs de la sous-classe : une redéfinition qui lit
this.itemsvoit doncundefined, et un appel commethis.items.push(x)lève uneTypeErrorà l'exécution. TypeScript ne le détecte pas. - Oublier
super(...)ou l'appeler trop tard. ErreursTS2377etTS17009; le parent doit construire l'objet avant que la sous-classe ne touche àthis. - Redéclarer un champ du parent sans
declare. Le champ de la sous-classe le remet àundefined. - Les hiérarchies profondes. Chaque niveau couple l'enfant aux détails internes du parent. Deux niveaux suffisent en général ; au-delà, préférez la composition ou les interfaces.
Questions fréquentes
Comment fonctionne l'héritage en TypeScript ?
Une classe hérite d'une autre avec extends : class Dog extends Animal. La sous-classe reçoit les champs et les méthodes du parent, appelle le constructeur parent avec super(...), et peut redéfinir des méthodes. TypeScript vérifie que les redéfinitions restent compatibles, au niveau des types, avec le parent.
Que fait le mot-clé override en TypeScript ?
override marque une méthode ou une propriété qui en remplace une de la classe de base. Si la classe de base n'a pas de membre de ce nom, le compilateur signale TS4117, ce qui détecte les fautes de frappe et les membres renommés dans le parent. Avec noImplicitOverride activé, chaque redéfinition doit porter le mot-clé (sinon TS4114).
Comment appeler une méthode de la classe parente en TypeScript ?
Utilisez super.methodName(...) dans la méthode de la sous-classe. Dans le constructeur, super(...) appelle le constructeur parent et doit s'exécuter avant toute utilisation de this.
TypeScript permet-il l'héritage multiple ?
Non. Une classe ne peut étendre qu'une seule classe (TS1174: Classes can only extend a single class). Elle peut implémenter autant d'interfaces qu'elle veut, et les mixins (des fonctions qui prennent une classe et renvoient une sous-classe) combinent des comportements venus de plusieurs sources.
Quelle est la différence entre extends et implements ?
extends hérite du vrai code d'une classe parente. implements vérifie seulement qu'une classe correspond à la forme d'une interface et n'ajoute rien à l'exécution. Une classe peut faire les deux : class Dog extends Animal implements Pet.