Microsoft annonce la disponibilité de TypeScript 5.5 Beta. Parmi les nouveautés : Prédicats de type inférés, réduction du flux de contrôle pour les accès indexés constants, importations de types dans JSDoc, vérification de la syntaxe des expressions régulières, et plus encore.Prédicats de type inférés
L'analyse du flux de contrôle de TypeScript fait un excellent travail de suivi de la façon dont le type d'une variable change au fur et à mesure qu'elle se déplace dans votre code :
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | interface Bird {
commonName: string;
scientificName: string;
sing(): void;
}
// Maps country names -> national bird.
// Not all nations have official birds (looking at you, Canada!)
declare const nationalBirds: Map<string, Bird>;
function makeNationalBirdCall(country: string) {
const bird = nationalBirds.get(country); // bird has a declared type of Bird | undefined
if (bird) {
bird.sing(); // bird has type Bird inside the if statement
} else {
// bird has type undefined here.
}
} |
Dans le passé, ce type de raffinement de type était plus difficile à appliquer aux tableaux. Cela aurait été une erreur dans toutes les versions précédentes de TypeScript :
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | function makeBirdCalls(countries: string[]) {
// birds: (Bird | undefined)[]
const birds = countries
.map(country => nationalBirds.get(country))
.filter(bird => bird !== undefined);
for (const bird of birds) {
bird.sing(); // error: 'bird' is possibly 'undefined'.
}
} |
Avec TypeScript 5.5, le vérificateur de type n'a rien à redire à ce code :
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | function makeBirdCalls(countries: string[]) {
// birds: Bird[]
const birds = countries
.map(country => nationalBirds.get(country))
.filter(bird => bird !== undefined);
for (const bird of birds) {
bird.sing(); // ok!
}
} |
Cela fonctionne parce que TypeScript déduit maintenant un prédicat de type pour la fonction de filtrage. Vous pouvez voir plus clairement ce qui se passe en la transformant en une fonction indépendante :
| Code : | Sélectionner tout |
1 2 3 4 | // function isBirdReal(bird: Bird | undefined): bird is Bird
function isBirdReal(bird: Bird | undefined) {
return bird !== undefined;
} |
TypeScript déduira qu'une fonction renvoie un prédicat de type si ces conditions sont remplies :
- La fonction n'a pas d'annotation explicite de type de retour ou de prédicat de type.
- La fonction a un seul énoncé de retour et aucun retour implicite.
- La fonction ne modifie pas son paramètre.
- La fonction renvoie une expression booléenne liée à un raffinement du paramètre.
En règle générale, tout se passe comme prévu. Voici quelques autres exemples de prédicats de types déduits :
| Code : | Sélectionner tout |
1 2 3 4 5 | // const isNumber: (x: unknown) => x is number const isNumber = (x: unknown) => typeof x === 'number'; // const isNonNullish: <T>(x: T) => x is NonNullable<T> const isNonNullish = <T,>(x: T) => x != null; |
Les prédicats de type ont une sémantique « si et seulement si ». Si une fonction renvoie x is T, cela signifie que :
- Si la fonction renvoie un résultat vrai, x est de type T.
- Si la fonction renvoie faux, x n'est pas de type T.
Si vous vous attendez à ce qu'un prédicat de type soit déduit mais qu'il ne l'est pas, vous risquez de vous heurter à la deuxième règle. Ce problème se pose souvent dans le cadre des vérifications de « véracité » :
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | function getClassroomAverage(students: string[], allScores: Map<string, number>) {
const studentScores = students
.map(student => allScores.get(student))
.filter(score => !!score);
return studentScores.reduce((a, b) => a + b) / studentScores.length;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// error: Object is possibly 'undefined'.
} |
Comme dans le premier exemple, il est préférable de filtrer explicitement les valeurs indéfinies :
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 | function getClassroomAverage(students: string[], allScores: Map<string, number>) {
const studentScores = students
.map(student => allScores.get(student))
.filter(score => score !== undefined);
return studentScores.reduce((a, b) => a + b) / studentScores.length; // ok!
} |
Les prédicats de type explicites continuent à fonctionner exactement comme avant. TypeScript ne vérifiera pas s'il déduit le même prédicat de type. Les prédicats de type explicites (« is ») ne sont pas plus sûrs qu'une assertion de type (« as »).
Il est possible que cette fonctionnalité casse du code existant si TypeScript déduit maintenant un type plus précis que ce que vous souhaitez. Par exemple :
| Code : | Sélectionner tout |
1 2 3 4 5 | // Previously, nums: (number | null)[] // Now, nums: number[] const nums = [1, 2, 3, null, 5].filter(x => x !== null); nums.push(null); // ok in TS 5.4, error in TS 5.5 |
| Code : | Sélectionner tout |
1 2 | const nums: (number | null)[] = [1, 2, 3, null, 5].filter(x => x !== null); nums.push(null); // ok in all versions |
Réduction du flux de contrôle pour les accès indexés constants
TypeScript est désormais capable de réduire les expressions de la forme obj[key] lorsque obj et key sont effectivement constants.
| Code : | Sélectionner tout |
1 2 3 4 5 6 | function f1(obj: Record<string, unknown>, key: string) {
if (typeof obj[key] === "string") {
// Now okay, previously was error
obj[key].toUpperCase();
}
} |
Importations de types dans JSDoc
Aujourd'hui, si vous voulez importer quelque chose uniquement pour vérifier le type dans un fichier JavaScript, c'est encombrant. Les développeurs JavaScript ne peuvent pas simplement importer un type nommé SomeType s'il n'est pas présent au moment de l'exécution.
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // ./some-module.d.ts
export interface SomeType {
// ...
}
// ./index.js
import { SomeType } from "./some-module"; // runtime error!
/**
* @param {SomeType} myValue
*/
function doSomething(myValue) {
// ...
} |
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 | import * as someModule from "./some-module";
/**
* @param {someModule.SomeType} myValue
*/
function doSomething(myValue) {
// ...
} |
Pour éviter cela, les développeurs devaient généralement utiliser des types import(...) dans les commentaires JSDoc.
| Code : | Sélectionner tout |
1 2 3 4 5 6 | /**
* @param {import("./some-module").SomeType} myValue
*/
function doSomething(myValue) {
// ...
} |
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | /**
* @typedef {import("./some-module").SomeType} SomeType
*/
/**
* @param {SomeType} myValue
*/
function doSomething(myValue) {
// ...
} |
C'est pourquoi TypeScript prend désormais en charge une nouvelle balise de commentaire @import qui a la même syntaxe que les importations ECMAScript.
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 | /** @import { SomeType } from "some-module" */
/**
* @param {SomeType} myValue
*/
function doSomething(myValue) {
// ...
} |
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.