Types d'écriture séparés sur les propriétés
En JavaScript, il est assez courant pour les API de convertir les valeurs transmises avant de les stocker. Cela arrive souvent aussi avec les getters et les setters. Par exemple, imaginons que nous ayons une classe avec un setter qui convertit toujours une valeur en number avant de l'enregistrer dans un champ privé.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Thing { #size = 0; get size() { return this.#size; } set size(value) { let num = Number(value); // Don't allow NaN and stuff. if (!Number.isFinite(num)) { this.#size = 0; return; } this.#size = num; } } |
Comment taper ce code JavaScript dans TypeScript ? Eh bien, techniquement, nous n'avons rien à faire de spécial ici - TypeScript peut regarder cela sans types explicites et peut comprendre que la size est un nombre.
Le problème est que la size vous permet de lui attribuer plus que de simples number. Nous pourrions contourner ce problème en disant que la size a le type unknown ou any comme dans cet extrait de code:
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 | class Thing { // ... get size(): unknown { return this.#size; } } |
Mais ce n’est pas bon - unknown oblige les personnes qui lisent size à faire une assertion de type, et any ne détecte d’erreur. Microsoft explique alors « si nous voulons vraiment modéliser des API qui convertissent des valeurs, les versions précédentes de TypeScript nous ont obligés à choisir entre être précis (ce qui facilite la lecture des valeurs et écrire plus dur) et être permissif (ce qui facilite l'écriture des valeurs et la lecture plus difficile) ».
C’est pourquoi TypeScript 4.3 vous permet de spécifier des types de lecture et d’écriture dans les propriétés.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Thing { #size = 0; get size(): number { return this.#size; } set size(value: string | number | boolean) { let num = Number(value); // Don't allow NaN and stuff. if (!Number.isFinite(num)) { this.#size = 0; return; } this.#size = num; } } |
Dans l'exemple ci-dessus, notre accesseur set prend un ensemble plus large de types (chaînes, booléens et nombres), mais notre accesseur get garantit toujours qu'il s'agira d'un nombre. Maintenant, nous pouvons enfin attribuer d'autres types à ces propriétés sans erreur!
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | let thing = new Thing(); // Assigning other types to `thing.size` works! thing.size = "hello"; thing.size = true; thing.size = 42; // Reading `thing.size` always produces a number! let mySize: number = thing.size; |
Lorsque l'on considère la relation entre deux propriétés portant le même nom, TypeScript n'utilisera que le type « lecture » (par exemple, le type sur l'accesseur get ci-dessus). Les types « écriture » ne sont pris en compte que lors de l'écriture directe dans une propriété.
Gardez à l'esprit qu'il ne s'agit pas d'un modèle limité aux classes. Vous pouvez écrire des getters et des setters avec différents types dans les littéraux d'objet.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function makeThing(): Thing { let size = 0; return { get size(): number { return size; }, set size(value: string | number | boolean) { let num = Number(value); // Don't allow NaN and stuff. if (!Number.isFinite(num)) { size = 0; return; } size = num; } } } |
En fait, Microsoft a ajouté une syntaxe aux interfaces / types d'objets pour prendre en charge différents types de lecture / écriture sur les propriétés.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 | // Now valid! interface Thing { get size(): number set size(value: number | string | boolean); } |
L’une des limites de l’utilisation de différents types pour la lecture et l’écriture de propriétés est que le type de lecture d’une propriété doit être attribuable au type que vous écrivez. En d'autres termes, le type getter doit être assignable au setter. Cela garantit un certain niveau de cohérence, de sorte qu'une propriété est toujours attribuable à elle-même.
override et --noImplicitOverride
Lorsque vous étendez des classes en JavaScript, le langage rend très facile le remplacement des méthodes - mais malheureusement, vous pouvez rencontrer des erreurs. Il manque un grand nombre de noms. Par exemple, prenez les classes suivantes:
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class SomeComponent { show() { // ... } hide() { // ... } } class SpecializedComponent extends SomeComponent { show() { // ... } hide() { // ... } } |
SpecializedComponent sous-classe SomeComponent et remplace les méthodes show et hide. Que se passe-t-il si quelqu'un décide de supprimer show et hide et de les remplacer par une seule méthode?
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class SomeComponent { - show() { - // ... - } - hide() { - // ... - } + setVisible(value: boolean) { + // ... + } } class SpecializedComponent extends SomeComponent { show() { // ... } hide() { // ... } } |
Notre SpecializedComponent n'a pas été mis à jour. Une partie du problème ici est qu'un utilisateur ne peut pas indiquer clairement s'il avait l'intention d'ajouter une nouvelle méthode ou de remplacer une méthode existante. C’est pourquoi TypeScript 4.3 ajoute le mot-clé override.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 | class SpecializedComponent extends SomeComponent { override show() { // ... } override hide() { // ... } } |
Lorsqu'une méthode est marquée par override, TypeScript s'assurera toujours qu'une méthode portant le même nom existe dans une classe de base.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class SomeComponent { setVisible(value: boolean) { // ... } } class SpecializedComponent extends SomeComponent { override show() { // ~~~~~~~~ // Error! This method can't be marked with 'override' because it's not declared in 'SomeComponent'. // ... } // ... } |
Il s’agit d’une grande amélioration, mais cela n’aide pas si vous oubliez d’écrire un override sur une méthode - et c’est également une grave erreur que les utilisateurs peuvent rencontrer.
Par exemple, vous pourriez accidentellement « piétiner » une méthode qui existe dans une classe de base sans vous en rendre compte.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Base { someHelperMethod() { // ... } } class Derived extends Base { // Oops! We weren't trying to override here, // we just needed to write a local helper method. someHelperMethod() { // ... } } |
C’est pourquoi TypeScript 4.3 fournit également un nouvel indicateur --noImplicitOverride. Lorsque cette option est activée, il devient une erreur de remplacer toute méthode d'une superclasse à moins que vous n'utilisiez explicitement un mot-clé override. Dans ce dernier exemple, TypeScript ferait une erreur sous --noImplicitOverride, et donnerait un indice indiquant qu'il faut probablement renommer notre méthode à l'intérieur de Derived.
Source : Microsoft