IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Microsoft annonce la disponibilité de TypeScript 5.4 Beta, avec l'introduction du type utilitaire NoInfer
Mais aussi les changements à venir suite aux dépréciations de TypeScript 5.0

Le , par Jade Emy

117PARTAGES

4  0 
Voici la présentation de TypeScript 5.4 Beta. Parmi les nouveautés : préservation du rétrécissement dans les fermetures suivant les dernières affectations, le type utilitaire NoInfer, le support des appels require() dans --moduleResolution bundler et --module preserve, et bien d'autres. Microsoft annonce aussi les changements à venir de la version 5.5 suite aux dépréciations de TypeScript 5.0.


Préservation du rétrécissement dans les fermetures après les dernières affectations

TypeScript peut généralement déterminer un type plus spécifique pour une variable en se basant sur les vérifications que vous pouvez effectuer. Ce processus est appelé rétrécissement.

Code : Sélectionner tout
1
2
3
4
5
6
function uppercaseStrings(x: string | number) {
    if (typeof x === "string") {
        // TypeScript knows 'x' is a 'string' here.
        return x.toUpperCase();
    }
}
Un problème courant était que ces types restreints n'étaient pas toujours préservés dans les fermetures de fonctions.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getUrls(url: string | URL, names: string[]) {
    if (typeof url === "string") {
        url = new URL(url);
    }

    return names.map(name => {
        url.searchParams.set("name", name)
        //  ~~~~~~~~~~~~
        // error!
        // Property 'searchParams' does not exist on type 'string | URL'.

        return url.toString();
    });
}
Ici, TypeScript a décidé qu'il n'était pas "sûr" de supposer que url était en fait un objet URL dans la fonction de rappel parce qu'il a été muté ailleurs ; cependant, dans ce cas, cette fonction arrow est toujours créée après l'affectation à url, et c'est aussi la dernière affectation à url.

TypeScript 5.4 tire parti de cette situation pour rendre le rétrécissement un peu plus intelligent. Lorsque des paramètres et des variables let sont utilisés dans des fonctions non hoisies, le vérificateur de type recherche un dernier point d'affectation. S'il en trouve un, TypeScript peut, en toute sécurité, rétrécir à partir de l'extérieur de la fonction contenante. Cela signifie que l'exemple ci-dessus fonctionne maintenant.

Notez que l'analyse de rétrécissement n'intervient pas si la variable est assignée n'importe où dans une fonction imbriquée. En effet, il n'y a aucun moyen de savoir avec certitude si la fonction sera appelée plus tard.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function printValueLater(value: string | undefined) {
    if (value === undefined) {
        value = "missing!";
    }

    setTimeout(() => {
        // Modifying 'value', even in a way that shouldn't affect
        // its type, will invalidate type refinements in closures.
        value = value;
    }, 500);

    setTimeout(() => {
        console.log(value.toUpperCase());
        //          ~~~~~
        // error! 'value' is possibly 'undefined'.
    }, 1000);
}
Cela devrait faciliter l'expression de nombreux codes JavaScript typiques.

Le type utilitaire NoInfer

Lors de l'appel de fonctions génériques, TypeScript est capable de déduire le type des arguments à partir de ce que vous lui passez.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
function doSomething<T>(arg: T) {
    // ...
}


// We can explicitly say that 'T' should be 'string'.
doSomething<string>("hello!");

// We can also just let the type of 'T' get inferred.
doSomething("hello!");
Cependant, il n'est pas toujours évident de savoir quel est le "meilleur" type à déduire. Cela peut conduire TypeScript à rejeter des appels valides, à accepter des appels douteux, ou tout simplement à envoyer des messages d'erreur plus mauvais lorsqu'il détecte un bogue.

Par exemple, imaginons une fonction createStreetLight qui prend une liste de noms de couleurs, ainsi qu'une couleur par défaut optionnelle.

Code : Sélectionner tout
1
2
3
4
5
function createStreetLight<C extends string>(colors: C[], defaultColor?: C) {
    // ...
}

createStreetLight(["red", "yellow", "green"], "red");
Que se passe-t-il lorsqu'on passe à defaultColor qui n'était pas dans le tableau de colors d'origine ? Dans cette fonction, colors est censé être la "source de vérité" et décrire ce qui peut être passé à defaultColor.

Code : Sélectionner tout
1
2
// Oops! This undesirable, but is allowed!
createStreetLight(["red", "yellow", "green"], "blue");
Dans cet appel, l'inférence de type a décidé que "blue" était un type tout aussi valide que "red" ou "yellow" ou "green". Ainsi, au lieu de rejeter l'appel, TypeScript déduit le type de C comme étant "rouge" | "jaune" | "vert" | "bleu". On pourrait dire que l'inférence est apparue comme un bleu dans la figure !

L'une des façons de résoudre ce problème est d'ajouter un paramètre de type séparé qui est délimité par le paramètre de type existant.

Code : Sélectionner tout
1
2
3
4
5
6
7
function createStreetLight<C extends string, D extends C>(colors: C[], defaultColor?: D) {
}

createStreetLight(["red", "yellow", "green"], "blue");
//                                            ~~~~~~
// error!
// Argument of type '"blue"' is not assignable to parameter of type '"red" | "yellow" | "green" | undefined'.
Cela fonctionne, mais c'est un peu gênant car D ne sera probablement utilisé nulle part ailleurs dans la signature de createStreetLight. Bien que ce ne soit pas mauvais dans ce cas, l'utilisation d'un paramètre de type une seule fois dans une signature est souvent une odeur de code.

C'est pourquoi TypeScript 5.4 introduit un nouveau type utilitaire NoInfer<T>. Entourer un type de NoInfer<...> indique à TypeScript de ne pas creuser et de ne pas comparer les types internes pour trouver des candidats à l'inférence de type.

En utilisant NoInfer, on peut réécrire createStreetLight comme suit :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
function createStreetLight<C extends string>(colors: C[], defaultColor?: NoInfer<C>) {
    // ...
}

createStreetLight(["red", "yellow", "green"], "blue");
//                                            ~~~~~~
// error!
// Argument of type '"blue"' is not assignable to parameter of type '"red" | "yellow" | "green" | undefined'.
Exclure le type defaultColor de l'exploration pour l'inférence signifie que "blue" ne se retrouve jamais comme candidat à l'inférence, et que le vérificateur de type peut le rejeter.

Object.groupBy et Map.groupBy

TypeScript 5.4 ajoute des déclarations pour les nouvelles méthodes statiques JavaScript Object.groupBy et Map.groupBy.

Object.groupBy prend un itérable et une fonction qui décide dans quel "groupe" chaque élément doit être placé. La fonction doit créer une "clé" pour chaque groupe distinct, et Object.groupBy utilise cette clé pour créer un objet où chaque clé correspond à un tableau contenant l'élément original.

Voici donc le JavaScript suivant :

Code : Sélectionner tout
1
2
3
4
5
const array = [0, 1, 2, 3, 4, 5];

const myObj = Object.groupBy(array, (num, index) => {
    return num % 2 === 0 ? "even": "odd";
});
équivaut en fait à écrire ceci :

Code : Sélectionner tout
1
2
3
4
const myObj = {
    even: [0, 2, 4],
    odd: [1, 3, 5],
};
Map.groupBy est similaire, mais produit une map au lieu d'un simple objet. Cela peut être plus souhaitable si vous avez besoin des garanties de Map, si vous travaillez avec des API qui attendent des Map, ou si vous avez besoin d'utiliser n'importe quel type de clé pour le regroupement - et pas seulement les clés qui peuvent être utilisées comme noms de propriétés en JavaScript.

Code : Sélectionner tout
1
2
3
const myObj = Map.groupBy(array, (num, index) => {
    return num % 2 === 0 ? "even" : "odd";
});
et comme précédemment, vous auriez pu créer myObj d'une manière équivalente :

Code : Sélectionner tout
1
2
3
4
const myObj = new Map();

myObj.set("even", [0, 2, 4]);
myObj.set("odd", [1, 3, 5]);
Notez que dans l'exemple ci-dessus d'Object.groupBy, l'objet produit utilise toutes les propriétés optionnelles.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
interface EvenOdds {
    even?: number[];
    odd?: number[];
}

const myObj: EvenOdds = Object.groupBy(...);

myObj.even;
//    ~~~~
// Error to access this under 'strictNullChecks'.
Ceci est dû au fait qu'il n'y a aucun moyen...
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.

Une erreur dans cette actualité ? Signalez-nous-la !