TypeScript 4.9 Beta est disponible et apporte la restriction des propriétés non listées avec l'opérateur in
ainsi que la vérification de l'égalité sur NaN
TypeScript, c'est un langage qui s'appuie sur JavaScript et ajoute une syntaxe pour les types. Dans un article publié sur son blog ce 23 septembre, Microsoft a annoncé la version bêta de TypeScript 4.9.
Les types aident à décrire les types de valeurs avec lesquelles vous travaillez et les types de fonctions que vous appelez. TypeScript peut utiliser ces informations pour vous aider à éviter les erreurs telles que les fautes de frappe, les arguments manquants ou l'oubli de vérifier null et undefined ! Mais cette vérification de type n'est pas la seule chose que fait TypeScript - il utilise les informations de ces types pour vous offrir une expérience d'édition, alimentant des éléments tels que la complétion de code, la définition, le changement de nom, etc.
Voici, ci-dessous, les nouveautés qu’apporte TypeScript 4.9 :
L'opérateur satisfies
Les développeurs TypeScript sont souvent confrontés à un dilemme : nous voulons nous assurer qu'une expression correspond à un certain type, mais nous voulons également conserver le type le plus spécifique de cette expression à des fins d'inférence. Par exemple :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 // Each property can be a string or an RGB tuple. const palette = { red: [255, 0, 0], green: "#00ff00", bleu: [0, 0, 255] // ^^^^ sacré bleu - we've made a typo! }; // We want to be able to use array methods on 'red'... const redComponent = palette.red.at(0); // or string methods on 'green'... const greenNormalized = palette.green.toUpperCase();
Remarquez qu'il est écrit bleu, alors qu'on aurait probablement dû écrire blue. La faute de frappe de bleu peut être corrigée en utilisant une annotation de type sur palette, mais des informations seraient perdues sur chaque propriété.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 type Colors = "red" | "green" | "blue"; type RGB = [red: number, green: number, blue: number]; const palette: Record<Colors, string | RGB> = { red: [255, 0, 0], green: "#00ff00", bleu: [0, 0, 255] // ~~~~ The typo is now correctly detected }; // But we now have an undesirable error here - 'palette.red' "could" be a string. const redComponent = palette.red.at(0);
Le nouvel opérateur satisfies permet de valider que le type d'une expression correspond à un certain type, sans modifier le type résultant de cette expression. Par exemple, nous pourrions utiliser satisfies pour valider que toutes les propriétés de palette sont compatibles avec string | number[] :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 type Colors = "red" | "green" | "blue"; type RGB = [red: number, green: number, blue: number]; const palette = { red: [255, 0, 0], green: "#00ff00", bleu: [0, 0, 255] // ~~~~ The typo is now caught! } satisfies Record<Colors, string | RGB>; // Both of these methods are still accessible! const redComponent = palette.red.at(0); const greenNormalized = palette.green.toUpperCase();
satisfies peut être utilisé pour détecter de nombreuses erreurs possibles. Par exemple, nous pouvons nous assurer qu'un objet possède toutes les clés d'un certain type, mais pas plus :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 type Colors = "red" | "green" | "blue"; // Ensure that we have exactly the keys from 'Colors'. const favoriteColors = { "red": "yes", "green": false, "blue": "kinda", "platypus": false // ~~~~~~~~~~ error - "platypus" was never listed in 'Colors'. } satisfies Record<Colors, unknown>; // All the information about the 'red', 'green', and 'blue' properties are retained. const g: boolean = favoriteColors.green;
Peut-être que nous ne nous soucions pas de savoir si les noms des propriétés correspondent d'une manière ou d'une autre, mais que nous nous soucions des types de chaque propriété. Dans ce cas, nous pouvons également nous assurer que toutes les valeurs des propriétés d'un objet sont conformes à un certain type.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12 type RGB = [red: number, green: number, blue: number]; const palette = { red: [255, 0, 0], green: "#00ff00", blue: [0, 0] // ~~~~~~ error! } satisfies Record<string, string | RGB>; // Information about each property is still maintained. const redComponent = palette.red.at(0); const greenNormalized = palette.green.toUpperCase();
Restriction des propriétés non listées avec l'opérateur in
En tant que développeurs, nous devons souvent traiter des valeurs qui ne sont pas entièrement connues au moment de l'exécution. En fait, il arrive souvent que nous ne sachions pas si des propriétés existent, que nous obtenions une réponse d'un serveur ou que nous lisions un fichier de configuration. L'opérateur in de JavaScript peut vérifier si une propriété existe sur un objet. Auparavant, TypeScript nous permettait d'éliminer tous les types qui ne listaient pas explicitement une propriété.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 interface RGB { red: number; green: number; blue: number; } interface HSV { hue: number; saturation: number; value: number; } function setColor(color: RGB | HSV) { if ("hue" in color) { // 'color' now has the type HSV } // ... }
Ici, le type RGB n'a pas listé la teinte et a été restreint, nous laissant avec le type HSV. Mais qu'en est-il des exemples où aucun type ne mentionne une propriété donnée ? Dans ces cas-là, le langage ne nous a pas beaucoup aidés. Prenons l'exemple suivant en JavaScript :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12 function tryGetPackageName(context) { const packageJSON = context.packageJSON; // Check to see if we have an object. if (packageJSON && typeof packageJSON === "object") { // Check to see if it has a string name property. if ("name" in packageJSON && typeof packageJSON.name === "string") { return packageJSON.name; } } return undefined; }
La réécriture en TypeScript canonique serait juste une question de définition et d'utilisation d'un type pour le contexte ; cependant, le choix d'un type fiable comme unknown pour la propriété packageJSON causerait des problèmes dans les anciennes versions de TypeScript.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 interface Context { packageJSON: unknown; } function tryGetPackageName(context: Context) { const packageJSON = context.packageJSON; // Check to see if we have an object. if (packageJSON && typeof packageJSON === "object") { // Check to see if it has a string name property. if ("name" in packageJSON && typeof packageJSON.name === "string") { // ~~~~ // error! Property 'name' does not exist on type 'object. return packageJSON.name; // ~~~~ // error! Property 'name' does not exist on type 'object. } } return undefined; }
En effet, alors que le type de packageJSON est passé d'inconnu à objet, l'opérateur in est strictement limité aux types qui définissent réellement la propriété vérifiée. Par conséquent, le type de packageJSON est resté objet. TypeScript 4.9 rend l'opérateur in un peu plus puissant lorsqu'il s'agit de restreindre les types qui ne répertorient pas du tout la propriété. Au lieu de les laisser tels quels, le langage intersectera leurs types avec Record<"property-key-being-checked", unknown>.
Ainsi, dans notre exemple, le type de packageJSON sera réduit d'inconnu à objet Record<"name", unknown>. Cela nous permet d'accéder directement à packageJSON.name et de le réduire indépendamment.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 interface Context { packageJSON: unknown; } function tryGetPackageName(context: Context): string | undefined { const packageJSON = context.packageJSON; // Check to see if we have an object. if (packageJSON && typeof packageJSON === "object") { // Check to see if it has a string name property. if ("name" in packageJSON && typeof packageJSON.name === "string") { // Just works! return packageJSON.name; } } return undefined; }
TypeScript 4.9 renforce également quelques contrôles sur la façon dont in est utilisé, en s'assurant que le côté gauche est assignable au type string | number | symbol, et que le côté droit est assignable à object. Cela permet de vérifier que nous utilisons des clés de propriété valides et que nous ne vérifions pas accidentellement des primitives.
Vérification de l'égalité sur NaN
Une des principales difficultés rencontrées par les développeurs JavaScript est la vérification de la valeur NaN à l'aide des opérateurs d'égalité intégrés. Pour rappel, NaN est une valeur numérique spéciale qui signifie Not a Number (pas un nombre). Rien n'est jamais égal à NaN - même NaN !
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 console.log(NaN == 0) // false console.log(NaN === 0) // false console.log(NaN == NaN) // false console.log(NaN === NaN) // false
Mais au moins symétriquement, tout est toujours non égal à NaN.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 console.log(NaN != 0) // true console.log(NaN !== 0) // true console.log(NaN != NaN) // true console.log(NaN !== NaN) // true
Techniquement, ce n'est pas un problème spécifique à JavaScript, puisque n'importe quel langage qui contient des flottants IEEE-754 a le même comportement ; mais le principal type numérique de JavaScript est un nombre à virgule flottante, et l'analyse des nombres en JavaScript peut souvent aboutir à NaN. A son tour, la vérification contre les NaN finit par être assez commune, et la manière correcte de le faire est d'utiliser Number.isNaN - mais comme nous l'avons mentionné, beaucoup de gens finissent accidentellement par vérifier avec someValue === NaN à la place.
TypeScript se trompe maintenant sur les comparaisons directes avec NaN, et suggère d'utiliser une variation de Number.isNaN à la place.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 function validate(someValue: number) { return someValue !== NaN; // ~~~~~~~~~~~~~~~~~ // error: This condition will always return 'true'. // Did you mean '!Number.isNaN(someValue)'? }
La surveillance des fichiers utilise désormais les événements du système de fichiers
Dans les versions précédentes, TypeScript s'appuyait fortement sur l'interrogation pour surveiller les fichiers individuels. L'utilisation d'une stratégie d'interrogation signifiait la vérification périodique de l'état d'un fichier pour les mises à jour. Sur Node.js, fs.watchFile est le moyen intégré d'obtenir une surveillance de fichier par sondage. Bien que l'interrogation ait tendance à être plus prévisible sur les plateformes et les systèmes de fichiers, cela signifie que votre CPU doit être interrompu périodiquement et vérifier les mises à jour du fichier, même si rien n'a changé. Pour quelques dizaines de fichiers, cela peut ne pas être perceptible, mais sur un projet plus important avec beaucoup de fichiers - ou beaucoup de fichiers dans les node_modules - cela peut devenir un monstre de ressources.
De manière générale, une meilleure approche consiste à utiliser les événements du système de fichiers. Au lieu de sonder, nous pouvons annoncer que nous sommes intéressés par les mises à jour de fichiers spécifiques et fournir un rappel lorsque ces fichiers sont effectivement modifiés. La plupart des plateformes modernes utilisées fournissent des facilités et des APIs comme CreateIoCompletionPort, kqueue, epoll et inotify. Node.js les rend abstraites en fournissant fs.watch. Les événements du système de fichiers fonctionnent généralement très bien, mais il y a beaucoup d'inconvénients à les utiliser et, par conséquent, à utiliser l'API fs.watch.
Un observateur doit faire attention à prendre en compte la surveillance des inodes, l'indisponibilité de certains systèmes de fichiers (par exemple les systèmes de fichiers en réseau), si la surveillance récursive des fichiers est disponible, si les renommages de répertoires déclenchent des événements, et même l'épuisement des observateurs de fichiers ! En d'autres termes, ce n'est pas tout à fait un repas gratuit, surtout si vous recherchez quelque chose de multiplateforme.
Par conséquent, Microsoft a choisi par défaut le plus petit dénominateur commun : l'interrogation. Pas toujours, mais la plupart du temps. Au fil du temps, Microsoft a fourni les moyens de choisir d'autres stratégies de surveillance des fichiers. Cela a permis d'obtenir un retour d'information et de renforcer l'implémentation de file-watching contre la plupart de ces problèmes spécifiques à la plateforme. Comme TypeScript a dû s'adapter à des bases de code plus importantes, et s'est amélioré dans ce domaine, nous avons estimé que le passage aux événements du système de fichiers par défaut serait un investissement rentable.
Dans TypeScript 4.9, la surveillance des fichiers est alimentée par les événements du système de fichiers par défaut, ne revenant à l'interrogation que si nous ne parvenons pas à mettre en place des surveillances basées sur des événements. Pour la plupart des développeurs, cela devrait fournir une expérience beaucoup moins gourmande en ressources lors de l'exécution en mode --watch, ou lors de l'exécution avec un éditeur alimenté par TypeScript comme Visual Studio ou VS Code.
Le mode de fonctionnement de la surveillance des fichiers peut toujours être configuré par le biais de variables d'environnement et de watchOptions - et certains éditeurs comme VS Code peuvent prendre en charge les watchOptions de manière indépendante. Les développeurs utilisant des configurations plus exotiques où le code source réside sur un système de fichiers en réseau (comme NFS et SMB) peuvent avoir besoin de revenir à l'ancien comportement ; bien que si un serveur a une puissance de traitement raisonnable, il pourrait être préférable d'activer SSH et d'exécuter TypeScript à distance afin qu'il ait un accès direct aux fichiers locaux. VS Code dispose de nombreuses extensions à distance pour faciliter cette tâche.
Corrections et modifications de rupture
Mises à jour de lib.d.ts
Alors que TypeScript s'efforce d'éviter les ruptures majeures, même les petits changements dans les bibliothèques intégrées peuvent causer des problèmes. Nous ne nous attendons pas à des ruptures majeures à la suite des mises à jour de DOM et lib.d.ts, mais il peut y avoir quelques petites ruptures.
Meilleurs types pour Promise.resolve
Promise.resolve utilise désormais le type Awaited pour déballer les types de type Promise qui lui sont transmis. Cela signifie qu'il renvoie plus souvent le bon type de Promise, mais ce type amélioré peut casser le code existant s'il s'attendait à un type quelconque ou inconnu au lieu d'une Promise.
JavaScript Emit n'élide plus les importations
Lorsque TypeScript a commencé à supporter la vérification de type et la compilation pour JavaScript, il a accidentellement supporté une fonctionnalité appelée import elision. En bref, si une importation n'est pas utilisée comme une valeur, ou si le compilateur peut détecter que l'importation ne se réfère pas à une valeur au moment de l'exécution, le compilateur laissera tomber l'importation pendant l'émission.
Ce comportement était discutable, en particulier la détection du fait que l'importation ne se réfère pas à une valeur, car cela signifie que TypeScript doit faire confiance aux fichiers de déclaration parfois imprécis. En contrepartie, TypeScript préserve désormais les importations dans les fichiers JavaScript.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 // Input: import { someValue, SomeClass } from "some-module"; /** @type {SomeClass} */ let val = someValue; // Previous Output: import { someValue } from "some-module"; /** @type {SomeClass} */ let val = someValue; // Current Output: import { someValue, SomeClass } from "some-module"; /** @type {SomeClass} */ let val = someValue;
exports est prioritaire sur typesVersions
Auparavant, TypeScript donnait incorrectement la priorité au champ typesVersions sur le champ exports lors de la résolution d'un package.json sous --moduleResolution node16. Si ce changement a un impact sur la bibliothèque, vous devrez peut-être ajouter des sélecteurs de version types@ dans le champ exports du package.json.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 { "type": "module", "main": "./dist/main.js" "typesVersions": { "<4.8": { ".": ["4.8-types/main.d.ts"] }, "*": { ".": ["modern-types/main.d.ts"] } }, "exports": { ".": { + "types@<4.8": "4.8-types/main.d.ts", + "types": "modern-types/main.d.ts", "import": "./dist/main.js" } } }
Substitute remplacé par constraint sur les SubstitutionTypes
Dans le cadre d'une optimisation des types de substitution, les objets SubstitutionType ne contiennent plus la propriété substitute représentant la substitution effective (généralement une intersection du type de base et de la contrainte implicite) - à la place, ils contiennent uniquement la propriété constraint.
Source : Microsoft
Voir aussi :
La version bêta de TypeScript 4.2 est disponible avec le type tuple plus amélioré et une préservation plus intelligente des alias de type
Microsoft publie TypeScript 4.2 avec la prise en charge de Abstract Constructor Types et des contrôles plus stricts pour l'opérateur in
Prisma : un ORM de nouvelle génération pour Node.js et TypeScript, pour concurrencer TypeORM et Sequelize et devenir la norme de l'industrie
Comment un développeur JavaScript anti-TypeScript est devenu un fan de TypeScript ? Voici les raisons de Chirag Swadia, un développeur JavaScript reconverti en développeur TypeScript
Partager