TypeScript est un langage de programmation libre et open source développé par Microsoft qui a pour but d'améliorer et de sécuriser la production de code JavaScript. Il s'agit d'un surensemble du JavaScript qui apporte un typage statique et optionnel des variables. Il permet de détecter certaines erreurs en amont et se compile en JavaScript pour une utilisation côté navigateur ou côté serveur à l'aide de NodeJS. Notons qu’avec l’utilitaire npm les développeurs JavaScript peuvent partager et réutiliser facilement leur code. Il facilite la mise à jour du code et est distribué avec Node.js. TypeScript présente plusieurs avantages :
- la prévention des bogues et la maintenabilité du code ;
- la prise en charge des interfaces, des sous-interfaces, des classes, des sous-classes ;
- la capacité de programmer en orienté objet avec l’héritage des membres privés et des interfaces.
Etant donné qu’il s’agit d’un langage qui s'appuie sur JavaScript et ajoute une syntaxe pour les types, ilpermet d'intégrer les attentes et les hypothèses dans le code, et ces hypothèses peuvent ensuite être vérifiées par le vérificateur de type TypeScript. Cette vérification peut aider à éviter les fautes de frappe, l'appel de valeurs non initialisées, le mélange d'arguments pour les fonctions, etc. Les types vont au-delà de la vérification et sont utilisés pour vous offrir une expérience d'édition puissante à la fois pour TypeScript et JavaScript, en permettant la complétion de code, les définitions de type, le renommage.
Voici, ci-dessous, les nouveautés apportées par TypeScript 4.8 :
Amélioration de la réduction des intersections, de la compatibilité des unions et de l'étroitesse du champ d'application
TypeScript 4.8 apporte une série d'améliorations de correction et de cohérence sous --strictNullChecks. Ces changements affectent la façon dont les types d'intersection et d'union fonctionnent, et sont exploités dans la façon dont TypeScript réduit les types. Par exemple, unknown est proche dans l'esprit du type union {} | null | undefined car il accepte null, undefined, et tout autre type. TypeScript reconnaît maintenant cela, et permet les affectations de unknown à {} | null | undefined.
Code : | 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 } |
Un autre changement est que l'intersection de {} avec tout autre type d'objet est simplifiée jusqu'à ce type d'objet. Cela signifie que nous avons pu réécrire NonNullable en utilisant simplement une intersection avec {}, car {} & null et {} & undefined sont simplement rejetés.
Code : | Sélectionner tout |
1 2 | - type NonNullable<T> = T extends null | undefined ? never : T; + type NonNullable<T> = T & {}; |
Il s'agit d'une amélioration car les types d'intersection comme celui-ci peuvent être réduits et assignés, alors que les types conditionnels ne le peuvent pas actuellement. Ainsi, NonNullable<NonNullable<T>> se simplifie maintenant au moins en NonNullable<T>, alors que ce n'était pas le cas auparavant.
Code : | 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 } |
Ces changements nous ont également permis d'apporter des améliorations sensibles à l'analyse du flux de contrôle et à la réduction des types. Par exemple, le type inconnu est maintenant réduit comme {} | null | undefined dans les branches véridiques.
Code : | 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 } } |
Les valeurs génériques sont également limitées de la même manière. Lorsqu'il vérifie qu'une valeur n'est pas nulle ou indéfinie, TypeScript la croise désormais avec {} - ce qui revient à dire qu'elle est NonNullable. En combinant les nombreux changements, nous pouvons maintenant définir la fonction suivante sans aucune assertion de type.
Code : | 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; } |
se réduit désormais à T & {}, et est désormais identique à NonNullable<T> - le corps de la fonction fonctionne donc sans syntaxe spécifique à TypeScript. En soi, ces changements peuvent sembler mineurs - mais ils représentent des corrections pour de nombreuses coupures de papier qui ont été signalées pendant plusieurs années.
Inférence améliorée pour les types d'inférence dans les types de chaînes de caractères de modèle
TypeScript a récemment introduit un moyen d'ajouter des contraintes étendues pour inférer les variables de type dans les types conditionnels.
Code : | 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; |
Si ces types déduits apparaissent dans un modèle de type chaîne et sont contraints à un type primitif, TypeScript essaiera désormais d'analyser un type littéral.
Code : | 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; |
Code : | 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; |
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 utilisant --build. Par exemple, TypeScript est maintenant capable d'éviter de passer du temps à mettre à jour les timestamps pendant les changements no-op en mode --watch, ce qui rend les reconstructions plus rapides et évite de perturber les autres outils de construction qui pourraient être à l'affût de la sortie de TypeScript. De nombreuses autres optimisations permettant de réutiliser les informations entre --build, --watch et --incremental ont également été introduites.
Quelle est l'importance de ces améliorations ? Eh bien, sur une base de code interne assez importante, nous avons constaté des réductions de temps de l'ordre de 10 à 25 % sur de nombreuses opérations communes simples, avec des réductions de temps d'environ 40 % dans des scénarios sans changement. Nous avons également obtenu des résultats similaires sur la base de code TypeScript.
Erreurs lors de la comparaison des littéraux d'objets et de tableaux
Dans de nombreux langages, des opérateurs comme == effectuent ce que l'on appelle une égalité de « valeur » sur les objets. Par exemple, en Python, il est possible de vérifier si une liste est vide en vérifiant si une valeur est égale à la liste vide en utilisant ==.
Code : | Sélectionner tout |
1 2 3 | if people_at_home == []: print("here's where I lie, broken inside. </3") adopt_animals() |
Ce n'est pas le cas en JavaScript, où == et === entre objets (et donc, tableaux) vérifient si les deux références pointent vers la même valeur. Nous pensons qu'un code similaire en JavaScript est, au mieux, un coup de pouce pour les développeurs JavaScript, et au pire, un bogue dans le code de production. C'est pourquoi TypeScript interdit désormais le code suivant.
Code : | Sélectionner tout |
1 2 3 4 5 6 | if (peopleAtHome === []) { // ~~~~~~~~~~~~~~~~~~~ // This condition will always return 'false' since JavaScript compares objects by reference, not value. console.log("here's where I lie, broken inside. </3") adoptAnimals(); } |
Inférence améliorée à partir des modèles de liaison
Dans certains cas, TypeScript récupère un type à partir d'un modèle de liaison pour effectuer de meilleures inférences.
Code : | Sélectionner tout |
declare function chooseRandomly<T>(x: T, y: T): T;
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 | let [a, b, c] = chooseRandomly([42, true, "hi!"], [0, false, "bye!"]); // ^ ^ ^ // | | | // | | string // | | // | boolean // | // number |
Cette méthode était bonne pour chooseRandomly, mais qu'elle était insuffisante dans d'autres cas. Par exemple, prenez le code suivant :
Code : | Sélectionner tout |
1 2 3 | declare function f<T>(x?: T): T; let [x, y, z] = f(); |
Le motif de let [x, y, z] indiquait que f devait produire un tuple [any, any, any] ; mais f ne devrait vraiment pas changer son argument de type en fonction d'un motif de let. Il ne peut pas soudainement faire apparaître une nouvelle valeur de type tableau en fonction de ce à quoi il est assigné, donc le type du motif de liaison a beaucoup trop d'influence sur le type produit. En outre, comme le type du motif de let est plein de anys, nous nous retrouvons avec x, y et z typés comme any.
Dans TypeScript 4.8, ces binding patterns 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écessite un type plus spécifique, comme dans notre exemple chooseRandomly. Si vous devez revenir à l'ancien comportement, il est toujours possible de fournir des arguments de type explicites.
Corrections de la surveillance des fichiers (en particulier à travers les checkouts git)
Nous avons eu un bug de longue date où TypeScript a un temps très difficile avec certains changements de fichiers en mode --watch et des scénarios d'éditeur. Parfois, les symptômes sont des erreurs erronées ou imprécises qui peuvent apparaître et qui nécessitent de redémarrer tsc ou VS Code. Ces erreurs se produisent fréquemment sur les systèmes Unix, et vous avez pu les voir 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 renommage sur les systèmes de fichiers. Les systèmes de fichiers utilisés par Linux et macOS utilisent des inodes, et Node.js attachera des surveillants de fichiers aux inodes plutôt qu'aux chemins de fichiers. Ainsi, lorsque Node.js renvoie un objet de surveillance, 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 watcher s'il détecte qu'un chemin existe toujours sur le disque. C'est là que les choses se gâtent, car même si un fichier existe toujours à ce chemin, un fichier distinct peut avoir été créé, et ce fichier aura un inode différent. Ainsi, TypeScript finissait par réutiliser l'objet watcher au lieu d'installer un nouveau watcher à l'emplacement d'origine, et de surveiller les changements dans ce qui pourrait être un fichier totalement non pertinent.
Donc TypeScript 4.8 gère maintenant ces cas sur les systèmes d'inode et installe correctement un nouveau watcher et corrige ce problème.
Amélioration des performances de Find-All-References
Lors de l'exécution de find-all-references dans votre éditeur, TypeScript est maintenant capable d'agir un peu plus intelligemment en agrégeant les références. Cela a réduit d'environ 20 % le temps que TypeScript a pris pour rechercher un identifiant largement utilisé dans sa propre base de code.
Exclusion de fichiers spécifiques des auto-importations
TypeScript 4.8 introduit une préférence d'éditeur pour exclure les fichiers des auto-importations. Dans Visual Studio Code, les noms de fichiers ou les expressions globales peuvent être ajoutés sous « Auto Import File Exclude Patterns » dans l'interface utilisateur des paramètres, ou dans un fichier .vscode/settings.json :
Code : | Sélectionner tout |
1 2 3 4 | { // Note that `javascript.preferences.autoImportFileExcludePatterns` can be specified for JavaScript too. "typescript.preferences.autoImportFileExcludePatterns": [ "**/node_modules/@types/node" ] } |
Corrections et modifications de rupture
En raison de la nature des changements de système de type, il y a très peu de changements qui peuvent être faits qui n'affectent pas un certain code ; cependant, il y a quelques changements qui sont plus susceptibles de nécessiter l'adaptation du code existant.
Mises à jour de lib.d.ts.
Les génériques non contraints ne sont plus assignables à {}
Dans TypeScript 4.8, pour les projets avec strictNullChecks activé, TypeScript émet désormais correctement une erreur lorsqu'un paramètre de type non contraint est utilisé dans une position où null ou undefined ne sont pas des valeurs légales. Cela inclut tout type qui s'attend à {}, objet, ou un type d'objet avec toutes les propriétés optionnelles.
Un exemple simple peut être vu dans ce qui suit.
Code : | 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); |
Comme démontré ci-dessus, un code comme celui-ci présente un bogue potentiel - les valeurs null et undefined peuvent être indirectement transmises par ces paramètres de type non contraints à un code qui n'est pas censé observer ces valeurs.
Ce comportement sera également visible dans les positions de type. En voici un exemple :
Code : | Sélectionner tout |
1 2 3 | interface Foo<T> { x: Bar<T>; } |
Code : | Sélectionner tout |
interface Bar<T extends {}> { }
Code : | Sélectionner tout |
1 2 | - function foo<T>(x: T) { + function foo<T extends {}>(x: T) { |
Code : | Sélectionner tout |
1 2 3 4 5 | function foo<T>(x: T) { + if (x !== null && x !== undefined) { bar(x); + } } |
Code : | Sélectionner tout |
1 2 3 4 | function foo<T>(x: T) { - bar(x); + bar(x!); } |
Les décorateurs sont placés sur les modificateurs des arbres syntaxiques de TypeScript
L'orientation actuelle des décorateurs dans TC39 signifie que TypeScript devra gérer une rupture en termes de placement des décorateurs. Auparavant, TypeScript supposait que les décorateurs seraient toujours placés avant tous les mots-clés/modificateurs. Par exemple
Code : | Sélectionner tout |
1 2 3 4 | @decorator export class Foo { // ... } |
Code : | Sélectionner tout |
1 2 3 | export @decorator class Foo { // ... } |
Pour ce faire, il expose un nouveau type alias appelé ModifierLike qui est un Modificateur ou un Décorateur.
Code : | Sélectionner tout |
export type ModifierLike = Modifier | Decorator;
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 | - readonly modifiers?: NodeArray<Modifier> | undefined; + /** + * @deprecated ... + * Use `ts.canHaveModifiers()` to test whether a `Node` can have modifiers. + * Use `ts.getModifiers()` to get the modifiers of a `Node`. + * ... + */ + readonly modifiers?: NodeArray<ModifierLike> | undefined; |
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 | - readonly decorators?: NodeArray<Decorator> | undefined; + /** + * @deprecated ... + * Use `ts.canHaveDecorators()` to test whether a `Node` can have decorators. + * Use `ts.getDecorators()` to get the decorators of a `Node`. + * ... + */ + readonly decorators?: undefined; |
Code : | Sélectionner tout |
1 2 3 4 5 | function canHaveModifiers(node: Node): node is HasModifiers; function getModifiers(node: HasModifiers): readonly Modifier[] | undefined; function canHaveDecorators(node: Node): node is HasDecorators; function getDecorators(node: HasDecorators): readonly Decorator[] | undefined; |
Code : | Sélectionner tout |
const modifiers = canHaveModifiers(myNode) ? getModifiers(myNode) : undefined;
Les types ne peuvent pas être importés/exportés dans les fichiers JavaScript
TypeScript autorisait auparavant les fichiers JavaScript à importer et exporter des entités déclarées avec un type, mais sans valeur, dans les déclarations d'importation et d'exportation. Ce comportement était incorrect, car les importations et exportations nommées pour des valeurs qui n'existent pas provoquent une erreur d'exécution sous les modules ECMAScript. Lorsqu'un fichier JavaScript est soumis à un contrôle de type sous --checkJs ou par le biais d'un commentaire // @ts-check, TypeScript émet désormais une erreur.
Code : | 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 }; |
Les patrons de liaison ne contribuent pas directement aux candidats à l'inférence
Comme mentionné ci-dessus, les binding patterns ne modifient plus le type de résultats d'inférence dans les appels de fonction. Les renommages inutilisés dans les binding patterns sont désormais des erreurs dans les signatures de type. La syntaxe d'annotation de type de TypeScript semble souvent pouvoir être utilisée pour déstructurer des valeurs. Par exemple, prenez la fonction suivante.
Code : | Sélectionner tout |
declare function makePerson({ name: string, age: number }): Person;
Dans une construction de type pure, l'écriture de ce type de code est inutile et constitue généralement une erreur car les développeurs supposent généralement qu'ils écrivent une annotation de type. TypeScript 4.8 fait de ces annotations une erreur, sauf si elles sont référencées plus tard dans la signature. La façon correcte d'écrire la signature ci-dessus serait la suivante :
Code : | Sélectionner tout |
1 2 3 4 5 | declare function makePerson(options: { name: string, age: number }): Person; // ou declare function makePerson({ name, age }: { name: string, age: number }): Person; |
Source : Microsoft
Et vous ?
Quel est votre avis sur le sujet ?
Voir aussi :
TypeScript 4.7 RC s'accompagne de la prise en charge du module ECMAScript dans Node.js et propose un contrôle de la détection de module
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