TypeScript 4.5 est disponible et apporte des améliorations au type Awaited
ainsi que le support de lib dans node_modules

Si vous n'êtes pas encore familiarisé avec TypeScript, il s'agit d'un langage qui s'appuie sur JavaScript en ajoutant des types vérifiés statiquement. Lorsque vous utilisez des types statiques, vous pouvez exécuter le compilateur TypeScript pour rechercher les bogues tels que les fautes de frappe et les incohérences dans les formes de vos données, et obtenir des suggestions pratiques. Ces types ne changent pas votre programme, et vous pouvez les supprimer pour vous conserver un JavaScript propre et lisible. Au-delà de la détection de bogues dans votre code, TypeScript vous aide également à écrire du code, car les types peuvent alimenter des outils utiles tels que la saisie semi-automatique, la définition et le renommage dans votre éditeur.

Les améliorations apportées au type Awaited et Promise

TypeScript 4.5 introduit un nouveau type d'utilitaire appelé type Awaited. Ce type est destiné à modéliser des opérations telles que les fonctions await et async, ou la méthode .then() sur les Promise (en particulier la manière dont elles déballent de manière récursive les Promise).

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
// A = string
type A = Awaited<Promise<string>>;
 
// B = number
type B = Awaited<Promise<Promise<number>>>;
 
// C = boolean | number
type C = Awaited<boolean | Promise<number>>;

Le type Awaited peut être utile pour modéliser les API existantes, y compris les composants JavaScript intégrés comme Promise.all, Promise.race, etc. En fait, certains des problèmes liés à l'inférence avec Promise.all ont motivé Awaited. Voici un exemple qui échoue dans TypeScript 4.4 ainsi que dans les versions antérieures :

Code TypeScript : 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
declare function MaybePromise<T>(value: T): T | Promise<T> | PromiseLike<T>;
 
async function doSomething(): Promise<[number, number]> {
    const result = await Promise.all([
        MaybePromise(100),
        MaybePromise(200)
    ]);
 
    // Error!
    //
    //    [number | Promise<100>, number | Promise<200>]
    //
    // is not assignable to type
    //
    //    [number, number]
    return result;
}

Désormais, Promise.all combine certaines fonctionnalités avec Awaited pour donner de bien meilleurs résultats d'inférence, et l'exemple ci-dessus fonctionne.

Prise en charge de lib à partir de node_modules

Pour garantir que la prise en charge de TypeScript et JavaScript fonctionne correctement, TypeScript regroupe une série de fichiers de déclaration (fichiers .d.ts). Ces fichiers de déclaration représentent les API disponibles dans le langage JavaScript et les API DOM de navigateur standard. Bien qu'il existe des valeurs par défaut raisonnables en fonction de votre cible, vous pouvez choisir les fichiers de déclaration que votre programme utilise en configurant le paramètre lib dans le fichier tsconfig.json.

Il y a cependant deux inconvénients occasionnels à inclure ces fichiers de déclaration avec TypeScript :
  • Lorsque vous mettez à niveau TypeScript, vous êtes également obligé de gérer les modifications apportées aux fichiers de déclaration intégrés de TypeScript, ce qui peut être un défi lorsque les API DOM changent aussi fréquemment qu'elles le font.
  • Il est difficile de personnaliser ces fichiers pour qu'ils correspondent à vos besoins avec les besoins des dépendances de votre projet (par exemple, si vos dépendances déclarent qu'elles utilisent les API DOM, vous pourriez également être obligé d'utiliser les API DOM).

TypeScript 4.5 introduit un moyen de remplacer une bibliothèque intégrée spécifique d'une manière similaire au fonctionnement du support de @types/. Au moment de décider quels fichiers lib TypeScript doit inclure, il recherchera d'abord un package @typescript/lib-* étendu dans node_modules. Par exemple, lors de l'inclusion de dom en tant qu'option dans lib, TypeScript utilisera les types dans node_modules/@typescript/lib-dom s'ils sont disponibles.

Vous pouvez ensuite utiliser votre gestionnaire de packages pour installer un package spécifique à prendre en charge pour une bibliothèque donnée. Par exemple, TypeScript publie aujourd'hui des versions des API DOM sur @types/web. Si vous souhaitez verrouiller votre projet sur une version spécifique des API DOM, vous pouvez l'ajouter à votre package.json*:

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
{
 "dependencies": {
    "@typescript/lib-dom": "npm:@types/web"
  }
}

Ensuite, à partir de la version 4.5, vous pouvez mettre à jour TypeScript et le fichier de verrouillage de votre gestionnaire de dépendances garantira qu'il utilise exactement la même version des types DOM. Cela signifie que vous pouvez mettre à jour vos types selon vos propres conditions.

Nom : type.png
Affichages : 49331
Taille : 1,8 Ko

Types de chaîne de template en tant que discriminants

TypeScript 4.5 peut désormais restreindre les valeurs qui ont des types de chaîne de template et reconnaît également les types de chaîne de template comme discriminants.

À titre d'exemple, les éléments suivants échouaient auparavant, mais les vérifications de type sont désormais réussies dans TypeScript 4.5 :

Code TypeScript : 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
export interface Success {
    type: `${string}Success`;
    body: string;
}
 
export interface Error {
    type: `${string}Error`;
    message: string;
}
 
export function handler(r: Success | Error) {
    if (r.type === "HttpSuccess") {
        // 'r' has type 'Success'
        let token = r.body;
    }
}

--module es2022

TypeScript prend désormais en charge un nouveau paramètre de module*: es2022. La principale fonctionnalité de --module es2022 est l'await du niveau supérieur, ce qui signifie que vous pouvez utiliser [C]await[/B] en dehors des fonctions async. Cela était déjà pris en charge dans --module esnext (et maintenant --module nodenext), mais es2022 est la première cible stable pour cette fonctionnalité.

Élimination de la récursivité de queue sur les types conditionnels

TypeScript doit souvent échouer gracieusement lorsqu'il détecte une récursivité éventuellement infinie, ou toute extension de type qui peut prendre beaucoup de temps et affecter votre expérience d'éditeur. En conséquence, TypeScript dispose d'heuristiques pour s'assurer qu'il ne déraille pas en essayant de séparer un type infiniment profond, ou en travaillant avec des types qui génèrent beaucoup de résultats intermédiaires.

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
type InfiniteBox<T> = { item: InfiniteBox<T> }
 
type Unpack<T> = T extends { item: infer U } ? Unpack<U> : T;
 
// error: Type instantiation is excessively deep and possibly infinite.
type Test = Unpack<InfiniteBox<number>>

L'exemple ci-dessus est intentionnellement simple et inutile, mais il existe de nombreux types qui sont réellement utiles et qui malheureusement déclenchent l'heuristique. Par exemple, le type TrimLeft suivant supprime les espaces au début d'un type de type chaîne. Si on lui donne un type de chaîne qui a un espace au début, il renvoie immédiatement le reste de la chaîne dans TrimLeft.

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
type TrimLeft<T extends string> =
    T extends ` ${infer Rest}` ? TrimLeft<Rest> : T;
 
// Test = "hello" | "world"
type Test = TrimLeft<"   hello" | " world">;

Ce type peut être utile, mais si une chaîne a 50 espaces de début, vous obtiendrez une erreur.

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
type TrimLeft<T extends string> =
    T extends ` ${infer Rest}` ? TrimLeft<Rest> : T;
 
// error: Type instantiation is excessively deep and possibly infinite.
type Test = TrimLeft<"                                                oops">;

C'est malheureux, car ces types de types ont tendance à être extrêmement utiles dans la modélisation d'opérations sur des chaînes - par exemple, des analyseurs pour les routeurs d'URL. Pour aggraver les choses, un type plus utile crée généralement plus d'instanciations de type, et à son tour a encore plus de limitations sur la longueur d'entrée.

TrimLeft est écrit d'une manière qui est récursive dans une branche. Lorsqu'il s'appelle à nouveau, il renvoie immédiatement le résultat et ne fait rien avec. Étant donné que ces types n'ont pas besoin de créer de résultats intermédiaires, ils peuvent être implémentés plus rapidement et d'une manière qui évite de déclencher de nombreuses heuristiques de récursivité de type intégrées à TypeScript.

C'est pourquoi TypeScript 4.5 effectue une certaine élimination de la récursion finale sur les types conditionnels. Tant qu'une branche d'un type conditionnel est simplement un autre type conditionnel, TypeScript peut éviter les instanciations intermédiaires. Il existe encore des heuristiques pour s'assurer que ces types ne dérapent pas, mais ils sont beaucoup plus généreux.

Gardez à l'esprit que le type suivant ne sera pas optimisé, car il utilise le résultat d'un type conditionnel en l'ajoutant à une union.

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
type GetChars<S> =
    S extends `${infer Char}${infer Rest}` ? Char | GetChars<Rest> : never;

Si vous souhaitez le rendre récursif en queue, vous pouvez introduire un assistant qui prend un paramètre de type « accumulateur », tout comme avec les fonctions récursives en queue.

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
type GetChars<S> = GetCharsHelper<S, never>;
type GetCharsHelper<S, Acc> =
    S extends `${infer Char}${infer Rest}` ? GetCharsHelper<Rest, Char | Acc> : Acc;

Désactivation de l'élision de l'importation

Dans certains cas, TypeScript ne peut pas détecter que vous utilisez une importation. Par exemple, prenons le code suivant*:

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
import { Animal } from "./animal.js";
 
eval("console.log(new Animal().isDangerous())");

Par défaut, TypeScript supprime toujours cette importation car elle semble être inutilisée. Dans TypeScript 4.5, vous pouvez activer un nouvel indicateur appelé --preserveValueImports pour empêcher TypeScript de supprimer toutes les valeurs importées de vos sorties JavaScript. Les bonnes raisons d'utiliser eval sont rares, mais quelque chose de très similaire se produit dans Svelte*:

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
<!-- A .svelte File -->
<script>
import { someFunc } from "./some-module.js";
</script>
 
<button on:click={someFunc}>Click me!</button>

avec dans Vue.js, en utilisant sa fonctionnalité <script setup>*:

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
<!-- A .vue File -->
<script setup>
import { someFunc } from "./some-module.js";
</script>
 
<button @click="someFunc">Click me!</button>

Ces frameworks génèrent du code basé sur le balisage en dehors de leurs balises <script>, mais TypeScript ne voit que le code dans les balises <script>. Cela signifie que TypeScript supprimera automatiquement l'importation de someFunc, et le code ci-dessus ne sera pas exécutable*! Avec TypeScript 4.5, vous pouvez utiliser --preserveValueImports pour éviter ces situations.

Notez que cet indicateur a une exigence particulière lorsqu'il est combiné avec --isolatedModules : les types importés doivent être marqués comme étant uniquement de type car les compilateurs qui traitent des fichiers uniques à la fois n'ont aucun moyen de savoir si les importations sont des valeurs qui semblent inutilisées, ou un type qui doit être supprimé afin d'éviter un crash d'exécution.

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
// Which of these is a value that should be preserved? tsc knows, but `ts.transpileModule`,
// ts-loader, esbuild, etc. don't, so `isolatedModules` gives an error.
import { someFunc, BaseType } from "./some-module.js";
//                 ^^^^^^^^
// Error: 'BaseType' is a type and must be imported using a type-only import
// when 'preserveValueImports' and 'isolatedModules' are both enabled.

Cela rend une autre fonctionnalité de TypeScript 4.5, les modificateurs de type sur les noms d'importation, particulièrement importante.

Source : Microsoft