Améliorations des performances de --build, --watch et --incremental TypeScript 4.8 introduit plusieurs optimisations qui devraient accélérer les scénarios autour de --watch et --incremental, ainsi que les constructions de références de projet à l'aide de --build. Par exemple, TypeScript est désormais en mesure d'éviter de passer du temps à mettre à jour les horodatages pendant les modifications sans opération en mode --watch, ce qui accélère les reconstructions et évite de déranger les autres outils de construction qui pourraient surveiller la sortie de TypeScript. De nombreuses autres optimisations permettant de réutiliser les informations dans --build, --watch et --incremental ont également été introduites.
Quelle est l'ampleur de ces améliorations*? Eh bien, sur une base de code interne assez importante, l'équipe TypeScript a constaté des réductions de temps de l'ordre de 10*% à 25*% sur de nombreuses opérations courantes simples, avec des réductions de temps d'environ 40*% dans des scénarios sans changement. L'équipe a également constaté des résultats similaires sur la base de code TypeScript.
Erreurs lors de la comparaison des littéraux d'objet et de tableau
Dans de nombreux langages, les opérateurs comme == effectuent ce qu'on appelle l'égalité de « valeur » sur les objets. Par exemple, en Python, il est valide de vérifier si une liste est vide en vérifiant si une valeur est égale à la liste vide en utilisant ==.
| Code Python : | Sélectionner tout |
1 2 | if people_at_home == []: print("that's where she lies, broken inside. </3") |
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 | let peopleAtHome = []; if (peopleAtHome === []) { // ~~~~~~~~~~~~~~~~~~~ // This condition will always return 'false' since JavaScript compares objects by reference, not value. console.log("that's where she lies, broken inside. </3") } |
Dans certains cas, TypeScript sélectionnera un type à partir d'un modèle de liaison pour faire de meilleures inférences.
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | declare function chooseRandomly<T>(x: T, y: T): T; let [a, b, c] = chooseRandomly([42, true, "hi!"], [0, false, "bye!"]); // ^ ^ ^ // | | | // | | string // | | // | boolean // | // number |
Vous pouvez voir à quel point cela était bon pour chooseRandomly, mais cela a échoué dans d'autres cas. Par exemple, prenez le code suivant
| Code TypeScript : | Sélectionner tout |
1 2 3 | declare function f<T>(x?: T): T; let [x, y, z] = f(); |
Dans TypeScript 4.8, ces modèles de liaison ne sont jamais utilisés comme candidats pour les arguments de type. Au lieu de cela, ils sont simplement consultés au cas où un paramètre nécessiterait un type plus spécifique, comme dans notre exemple chooseRandomly. Si vous devez revenir à l'ancien comportement, vous pouvez toujours fournir des arguments de type explicites.
Inférence améliorée pour les types d'inférence dans les types de chaîne de template
TypeScript a récemment introduit un moyen d'ajouter des contraintes d'extension pour déduire des variables de type dans des types conditionnels.
| Code TypeScript : | Sélectionner tout |
1 2 3 4 | // Grabs the first element of a tuple if it's assignable to 'number', // and returns 'never' if it can't find one. type TryGetNumberIfFirst<T> = T extends [infer U extends number, ...unknown[]] ? U : never; |
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 | // SomeNum used to be 'number'; now it's '100'. type SomeNum = "100" extends `${infer U extends number}` ? U : never; // SomeBigInt used to be 'bigint'; now it's '100n'. type SomeBigInt = "100" extends `${infer U extends bigint}` ? U : never; // SomeBool used to be 'boolean'; now it's 'true'. type SomeBool = "true" extends `${infer U extends boolean}` ? U : never; |
Une note à ce sujet est que lorsque TypeScript analyse ces types littéraux, il essaiera avidement d'analyser autant de ce qui ressemble au type primitif approprié; cependant, il vérifie ensuite si la réimpression de cette primitive correspond au contenu de la chaîne. En d'autres termes, TypeScript vérifie si le passage de la chaîne à la primitive et le retour correspondent. Si ce n'est pas le cas, alors il reviendra au type primitif de base.
| Code TypeScript : | Sélectionner tout |
1 2 | // JustNumber is `number` here because TypeScript parses out `"1.0"`, but `String(Number("1.0"))` is `"1"` and doesn't match. type JustNumber = "1.0" extends `${infer T extends number}` ? T : never; |
TypeScript 4.8 apporte une série d'améliorations d'exactitude et de cohérence sous --strictNullChecks. Ces changements affectent le fonctionnement des types d'intersection et d'union et sont exploités dans la façon dont TypeScript restreint les types.
Par exemple, unknown est proche dans l'esprit du type d'union {} | nul | undefined car il accepte null, undefined et tout autre type. TypeScript le reconnaît désormais et autorise les affectations de unknown à {} | null | undefined.
| Code TypeScript : | Sélectionner tout |
1 2 3 4 | function f(x: unknown, y: {} | null | undefined) { x = y; // always worked y = x; // used to error, now works } |
| Code TypeScript : | Sélectionner tout |
1 2 | - type NonNullable<T> = T extends null | undefined ? never : T; + type NonNullable<T> = T & {}; |
| Code TypeScript : | Sélectionner tout |
1 2 3 4 | function foo<T>(x: NonNullable<T>, y: NonNullable<NonNullable<T>>) { x = y; // always worked y = x; // used to error, now works } |
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | function narrowUnknownishUnion(x: {} | null | undefined) { if (x) { x; // {} } else { x; // {} | null | undefined } } function narrowUnknown(x: unknown) { if (x) { x; // used to be 'unknown', now '{}' } else { x; // unknown } } |
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | function throwIfNullable<T>(value: T): NonNullable<T> { if (value === undefined || value === null) { throw Error("Nullable value!"); } // Used to fail because 'T' was not assignable to 'NonNullable<T>'. // Now narrows to 'T & {}' and succeeds because that's just 'NonNullable<T>'. return value; } |
En eux-mêmes, ces changements peuvent sembler minimes, mais ils représentent des correctifs pour de nombreux cas qui ont été signalés sur plusieurs années.
Corrections de surveillance de fichiers (en particulier lors des vérifications git)
L'équipe Typescript reconnaît avoir eu un bogue de longue date où TypeScript a beaucoup de mal avec certaines modifications de fichiers dans le mode --watch et les scénarios d'éditeur. Parfois, les symptômes sont des erreurs obsolètes ou inexactes qui peuvent apparaître et nécessiter le redémarrage de tsc ou de VS Code. Ceux-ci se produisent fréquemment sur les systèmes Unix, et vous les avez peut-être vus après avoir enregistré un fichier avec vim ou échangé des branches dans git.
Cela a été causé par des hypothèses sur la façon dont Node.js gère les événements de changement de nom dans les systèmes de fichiers. Les systèmes de fichiers utilisés par Linux et macOS utilisent des inodes, et Node.js attachera des observateurs de fichiers aux inodes plutôt qu'aux chemins de fichiers. Ainsi, lorsque Node.js renvoie un objet observateur, il peut surveiller un chemin ou un inode en fonction de la plate-forme et du système de fichiers.
Pour être un peu plus efficace, TypeScript essaie de réutiliser les mêmes objets observateurs s'il détecte qu'un chemin existe toujours sur le disque. C'est là que les choses se sont mal passées, car même si un fichier existe toujours sur ce chemin, un fichier distinct peut avoir été créé et ce fichier aura un inode différent. Ainsi, TypeScript finirait par réutiliser l'objet observateur au lieu d'installer un nouvel observateur à l'emplacement d'origine, et surveillerait les modifications apportées à ce qui pourrait être un fichier totalement non pertinent. Ainsi, TypeScript 4.8 gère désormais ces cas sur les systèmes inode et installe correctement un nouvel observateur et corrige cela.
Améliorations des performances de la recherche de toutes les références
Lors de l'exécution de la recherche de toutes les références dans votre éditeur, TypeScript est désormais capable d'agir un peu plus intelligemment lorsqu'il agrège les références. Cela a réduit d'environ 20*% le temps nécessaire à TypeScript pour rechercher un identifiant largement utilisé dans sa propre base de code.
Changements substanciels
En raison de la nature des modifications du système de type, très peu de modifications peuvent être apportées sans affecter certains codes*; cependant, il y a quelques changements qui sont plus susceptibles de nécessiter l'adaptation du code existant.
Mises à jour de lib.d.ts
Alors que TypeScript s'efforce d'éviter les ruptures majeures, même de petites modifications dans les bibliothèques intégrées peuvent causer des problèmes. L'équipe ne prévoit pas de ruptures majeures à la suite des mises à jour de DOM et de lib.d.ts, mais il peut y en avoir de petites.
Les génériques sans contrainte ne peuvent plus être attribués à {}
Dans TypeScript 4.8, pour les projets avec strictNullChecks activé, TypeScript émet désormais correctement une erreur lorsqu'un paramètre de type sans contrainte est utilisé dans une position où null ou undefined ne sont pas des valeurs légales. Cela inclura tout type qui attend {}, un objet ou un type d'objet avec des propriétés entièrement facultatives.
Un exemple simple peut être vu dans ce qui suit :
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | // Accepts any non-null non-undefined value function bar(value: {}) { Object.keys(value); // This call throws on null/undefined at runtime. } // Unconstrained type parameter T... function foo<T>(x: T) { bar(x); // Used to be allowed, now is an error in 4.8. // ~ // error: Argument of type 'T' is not assignable to parameter of type '{}'. } foo(undefined); |
Ce comportement sera également visible dans les positions de type. Un exemple serait :
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 | interface Foo<T> { x: Bar<T>; } interface Bar<T extends {}> { } |
| Code TypeScript : | Sélectionner tout |
1 2 | - function foo<T>(x: T) { + function foo<T extends {}>(x: T) { |
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 | function foo<T>(x: T) { + if (x !== null && x !== undefined) { bar(x); + } } |
| Code TypeScript : | Sélectionner tout |
1 2 3 4 | function foo<T>(x: T) { - bar(x); + bar(x!); } |
TypeScript permettait auparavant aux fichiers JavaScript d'importer et d'exporter des entités déclarées avec un type, mais sans valeur, dans les instructions d'importation et d'exportation. Ce comportement était incorrect, car les importations et les exportations nommées pour des valeurs qui n'existent pas entraîneront une erreur d'exécution sous les modules ECMAScript. Lorsqu'un fichier JavaScript est vérifié par type sous --checkJs ou via un commentaire // @ts-check, TypeScript émet désormais une erreur.
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // @ts-check // Will fail at runtime because 'SomeType' is not a value. import { someValue, SomeType } from "some-module"; /** * @type {SomeType} */ export const myValue = someValue; /** * @typedef {string | number} MyType */ // Will fail at runtime because 'MyType' is not a value. export { MyType as MyExportedType }; |
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 | - import { someValue, SomeType } from "some-module"; + import { someValue } from "some-module"; /** - * @type {SomeType} + * @type {import("some-module").SomeType} */ export const myValue = someValue; |
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 | /** * @typedef {string | number} MyType */ + /** + * @typedef {MyType} MyExportedType + */ - export { MyType as MyExportedType }; |
Vous avez lu gratuitement 22 articles depuis plus d'un an.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.