Menu

Classes en TypeScript : champs, méthodes, getters et implements

Les classes TypeScript sont des classes JavaScript avec des champs, des méthodes et des constructeurs typés. Découvrez le fonctionnement des déclarations de champs et de strictPropertyInitialization, comment typer this, les getters et setters, les membres statiques, implements, et comment une classe sert aussi de type.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Une classe TypeScript est une classe JavaScript avec des types : chaque champ est déclaré avec son type dans le corps de la classe, et les méthodes comme le constructeur reçoivent des paramètres et des valeurs de retour typés.

La sortie compilée est une classe JavaScript ordinaire sans les types : tout ce qui concerne les classes JavaScript reste donc valable. Ce qu'ajoute TypeScript se passe à la compilation : le dernier appel est refusé avant l'exécution du programme.

Déclarer des champs

Un champ doit être déclaré dans le corps de la classe avant que le constructeur puisse l'assigner. Assigner this.owner sans déclarer owner provoque l'erreur TS2339 (la propriété n'existe pas). Un champ avec un initialiseur voit son type inféré à partir de la valeur, comme une variable.

DéclarationSignification
name: string;Doit être assigné dans le constructeur
count = 0;Initialisé, type inféré en number
label?: string;Optionnel, de type string | undefined
readonly id: number;Assigné une fois, puis en lecture seule
data!: string[];Assignation définie : faites-moi confiance, il sera défini
static total = 0;Appartient à la classe, pas aux instances

Avec strict activé, strictPropertyInitialization vérifie que chaque champ non optionnel reçoit une valeur. Cette classe en oublie deux :

Le compilateur signale les deux :

index.ts(3,5): error TS2564: Property 'name' has no initializer and is not definitely assigned in the constructor.
index.ts(4,5): error TS2564: Property 'age' has no initializer and is not definitely assigned in the constructor.

Cette vérification existe parce que, sans elle, new Profile().name.toUpperCase() passerait la vérification des types puis planterait sur undefined. Corrigez chaque champ de l'une de ces quatre façons : donnez-lui un initialiseur, assignez-le dans le constructeur, rendez-le optionnel avec ?, ou écrivez name!: string quand un framework ou une méthode init() le définit plus tard. La dernière option désactive la vérification pour ce champ : utilisez-la avec parcimonie.

Méthodes et this

Les méthodes se typent comme des fonctions. Dans une méthode, this a le type de l'instance. Une méthode peut renvoyer this pour permettre le chaînage, et le type de retour suit automatiquement les sous-classes.

class QueryBuilder {
    private parts: string[] = [];

    where(condition: string): this {
        this.parts.push(condition);
        return this;
    }

    build(): string {
        return this.parts.join(" AND ");
    }
}

const sql = new QueryBuilder().where("age > 18").where("active = 1").build();
console.log(sql); // age > 18 AND active = 1

Une méthode perd son this quand on la détache de l'objet, un bug classique de JavaScript. TypeScript ne le détecte pas par défaut, mais il le fait si la méthode déclare un paramètre this. Ce paramètre est effacé de la sortie et ne sert qu'à protéger l'appel :

Le champ fonction fléchée fonctionne parce que chaque instance reçoit sa propre fonction, qui capture this. Le coût est une fonction par instance au lieu d'une méthode partagée sur le prototype.

Getters et setters

Les accesseurs get et set ressemblent à des propriétés vus de l'extérieur. Un getter sans setter est en lecture seule : y assigner une valeur est une erreur de compilation (TS2540).

Le compilateur refuse l'assignation, et si la vérification est contournée, l'exécution lève aussi une TypeError, car le fichier compilé s'exécute en mode strict (le compilateur émet "use strict" en haut du fichier). La RangeError du setter est une vérification à l'exécution que vous avez écrite vous-même. Le type number dit seulement que la valeur est un nombre ; il ne sait rien des plages de valeurs.

Membres statiques

Les champs et méthodes static appartiennent à la classe elle-même. Vous y accédez via le nom de la classe, pas via une instance. Un bloc static { } s'exécute une fois, quand la classe est définie.

Les membres statiques ne peuvent pas utiliser les paramètres de type de la classe : dans class Box<T>, un static empty: T provoque l'erreur TS2302, car il n'existe qu'un seul champ statique, partagé par chaque Box<string>, Box<number> et ainsi de suite.

Implémenter une interface

implements demande au compilateur de vérifier que la classe possède tout ce qu'exige une interface. Un membre manquant provoque l'erreur TS2420 : Class 'X' incorrectly implements interface 'Y'.

Une classe peut implémenter plusieurs interfaces : class Doc implements Printable, Serializable. Il y a deux choses que implements ne fait pas. Il ne type pas pour vous les paramètres des méthodes de la classe : check(s) { ... } dans une classe qui implémente check(s: string): boolean provoque toujours l'erreur TS7006 (Parameter 's' implicitly has an 'any' type), annotez-les donc. Et il ne laisse aucune trace à l'exécution : obj instanceof Shape est donc impossible.

Le private radius du constructeur est une propriété de paramètre : il déclare et assigne le champ en une seule étape. Voir constructeurs.

Une classe est aussi un type

Une déclaration de classe crée deux choses sous un même nom : une valeur (la fonction constructeur que vous appelez avec new) et un type (la forme d'une instance). Comme TypeScript compare les types par leur structure, tout objet ayant les mêmes membres publics correspond au type, même s'il n'a pas été créé par la classe.

L'objet simple passe la vérification des types mais n'est pas un Point à l'exécution : ({ x: 6, y: 8 }) instanceof Point vaut false. Une classe dotée d'un membre private ou #private empêche cela : seules les instances de cette classe (ou de ses sous-classes) lui sont assignables.

Erreurs fréquentes

  • Déclarer un champ sans jamais l'assigner. TS2564 vous indique que le champ vaudrait undefined. Initialisez-le plutôt que de le faire taire avec !.
  • Passer une méthode comme callback. button.onclick = obj.handle perd this. Utilisez un champ fonction fléchée ou obj.handle.bind(obj).
  • Attendre de implements qu'il ajoute du code ou des types. Il ne fait que vérifier. Les types des paramètres ont toujours besoin d'annotations.
  • Utiliser instanceof avec une interface. Les interfaces n'existent pas à l'exécution ; vérifiez une classe ou utilisez un type guard.
  • Supposer que les types valident les données. Un champ number accepte n'importe quel nombre à la compilation, et absolument n'importe quoi venant de données non typées à l'exécution.

Questions fréquentes

Comment créer une classe en TypeScript ?

Déclarez chaque champ avec son type dans le corps de la classe, puis écrivez le constructeur et les méthodes comme en JavaScript : class User { name: string; constructor(name: string) { this.name = name; } greet(): string { return "Hi, " + this.name; } }. Créez une instance avec new User("Ada").

Que signifie « has no initializer and is not definitely assigned in the constructor » ?

C'est l'erreur TS2564 de strictPropertyInitialization (qui fait partie de strict). Un champ typé string commencerait à undefined, car rien ne le définit. Corrigez avec un initialiseur (name = ""), une assignation dans le constructeur, un champ optionnel (name?: string), ou, quand quelque chose hors du constructeur le définit, une assertion d'assignation définie (name!: string).

Quelle est la différence entre implements et extends en TypeScript ?

extends hérite du code d'une classe parente : ses champs et ses méthodes existent sur l'enfant à l'exécution. implements demande seulement au compilateur de vérifier que la classe a la forme d'une interface. Il n'ajoute rien à la classe et disparaît du JavaScript compilé.

Une classe peut-elle servir de type en TypeScript ?

Oui. Un nom de classe est à la fois une valeur (le constructeur) et un type (la forme de ses instances). let u: User accepte n'importe quel objet ayant les mêmes membres publics, car TypeScript compare les types par leur structure. typeof User est le type du constructeur lui-même.

Les classes TypeScript diffèrent-elles des classes JavaScript à l'exécution ?

Non. TypeScript compile une classe en une classe JavaScript normale. Les annotations de type, les clauses implements et les modificateurs comme private sont effacés ; seules les fonctionnalités JavaScript comme les champs #private, les blocs static et les getters existent à l'exécution.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER