Microsoft vient de publier la version bêta de TypeScript 5.8. Cette version améliore la façon dont les types de retour sont vérifiés pour les types d'accès conditionnels et indexés, optimise l'interopérabilité des modules dans Node.js, et inclut des améliorations de performance pour une résolution plus rapide des projets.Pour commencer à utiliser la version bêta, vous pouvez l'obtenir via npm avec la commande suivante :
| Code : | Sélectionner tout |
npm install -D typescript@beta
Retours vérifiés pour les types d'accès conditionnels et indexés
Considérons une API qui présente un ensemble d'options à un utilisateur :
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /**
* @param prompt The text to show to a user.
* @param selectionKind Whether a user can select multiple options, or just a single option.
* @param items Each of the options presented to the user.
**/
async function showQuickPick(
prompt: string,
selectionKind: SelectionKind,
items: readonly string[],
): Promise<string | string[]> {
// ...
}
enum SelectionKind {
Single,
Multiple,
} |
L'objectif de showQuickPick est d'afficher un élément d'interface utilisateur qui permet de sélectionner une ou plusieurs options. Le moment où il le fait est déterminé par le paramètre selectionKind. Lorsque selectionKind est SelectionKind.Single, le type de retour de showQuickPick doit être string, et lorsqu'il est SelectionKind.Multiple, le type de retour doit être string[].
Le problème est que la signature de type de showQuickPick n'est pas claire. Elle indique simplement que le type retourné est string | string[] - il pourrait s'agir d'un string ou d'une string[], mais l'appelant doit vérifier explicitement. Dans l'exemple ci-dessous, on pourrait s'attendre à ce que shoppingList ait le type string[], mais on se retrouve avec le type plus large string | string[].
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | let shoppingList = await showQuickPick(
"Which fruits do you want to purchase?",
SelectionKind.Multiple,
["apples", "oranges", "bananas", "durian"],
);
console.log(`Alright, going out to buy some ${shoppingList.join(", ")}`);
// ~~~~
// error!
// Property 'join' does not exist on type 'string | string[]'.
// Property 'join' does not exist on type 'string'. |
Au lieu de cela, nous pouvons utiliser un type conditionnel pour rendre le type de retour de showQuickPick plus précis :
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | type QuickPickReturn<S extends SelectionKind> =
S extends SelectionKind.Multiple ? string[] : string
async function showQuickPick<S extends SelectionKind>(
prompt: string,
selectionKind: S,
items: readonly string[],
): Promise<QuickPickReturn<S>> {
// ...
} |
Cela fonctionne bien pour les appelants.
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | // `SelectionKind.Multiple` gives a `string[]` - works
let shoppingList: string[] = await showQuickPick(
"Which fruits do you want to purchase?",
SelectionKind.Multiple,
["apples", "oranges", "bananas", "durian"],
);
// `SelectionKind.Single` gives a `string` - works
let dinner: string = await showQuickPick(
"What's for dinner tonight?",
SelectionKind.Single,
["sushi", "pasta", "tacos", "ugh I'm too hungry to think, whatever you want"],
); |
Mais qu'en est-il si nous essayons d'implémenter showQuickPick ?
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | async function showQuickPick<S extends SelectionKind>(
prompt: string,
selectionKind: S,
items: readonly string[],
): Promise<QuickPickReturn<S>> {
if (items.length < 1) {
throw new Error("At least one item must be provided.");
}
// Create buttons for every option.
let buttons = items.map(item => ({
selected: false,
text: item,
}));
// Default to the first element if necessary.
if (selectionKind === SelectionKind.Single) {
buttons[0].selected = true;
}
// Event handling code goes here...
// Figure out the selected items
const selectedItems = buttons
.filter(button => button.selected)
.map(button => button.text);
if (selectionKind === SelectionKind.Single) {
// Pick the first (only) selected item.
return selectedItems[0];
}
else {
// Return all selected items.
return selectedItems;
}
} |
Malheureusement, TypeScript émet une erreur sur chacune des instructions de retour.
| Code : | Sélectionner tout |
1 2 | Type 'string[]' is not assignable to type 'QuickPickReturn<S>'. Type 'string' is not assignable to type 'QuickPickReturn<S>'. |
Jusqu'à présent, TypeScript exigeait une assertion de type pour implémenter toute fonction renvoyant un type conditionnel d'ordre supérieur.
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | if (selectionKind === SelectionKind.Single) {
// Pick the first (only) selected item.
- return selectedItems[0];
+ return selectedItems[0] as QuickPickReturn<S>;
}
else {
// Return all selected items.
- return selectedItems;
+ return selectedItems as QuickPickReturn<S>;
} |
Cette situation n'est pas idéale car les assertions de type annulent les vérifications légitimes que TypeScript effectuerait autrement. Par exemple, il serait idéal que TypeScript puisse détecter le bogue suivant où chaque branche du if/else est mélangée :
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 | if (selectionKind === SelectionKind.Single) {
// Oops! Returning an array when the caller expects a single item!
return selectedItems;
}
else {
// Oops! Returning a single item when the caller expects an array!
return selectedItems[0];
} |
Pour éviter les assertions de type, TypeScript 5.8 prend désormais en charge une forme limitée de vérification des types conditionnels dans les instructions de retour. Lorsque le type de retour d'une fonction est un type conditionnel générique, TypeScript utilise désormais l'analyse du flux de contrôle pour les paramètres génériques dont les types sont utilisés dans le type conditionnel, instancie le type conditionnel avec le type réduit de chaque paramètre, et effectue une vérification par rapport à ce nouveau type.
Qu'est-ce que cela signifie en pratique ? Tout d'abord, examinons quels genres de types conditionnels impliquent une réduction. Pour refléter la façon dont la réduction opère dans les expressions, nous devons être plus explicites et exhaustifs sur ce qui se passe dans chaque branche.
| Code : | Sélectionner tout |
1 2 3 4 | type QuickPickReturn<S extends SelectionKind> =
S extends SelectionKind.Multiple ? string[] :
S extends SelectionKind.Single ? string :
never; |
Une fois que c'est fait, tout fonctionne dans l'exemple que nous venons de donner. Les appelants n'ont aucun problème, et l'implémentation est maintenant sûre. Et si l'on essaie d'intervertir le contenu des branches du if, TypeScript le signale correctement comme une erreur.
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | if (selectionKind === SelectionKind.Single) {
// Oops! Returning an array when the caller expects a single item!
return selectedItems;
// ~~~~~~
// error! Type 'string[]' is not assignable to type 'string'.
}
else {
// Oops! Returning a single item when the caller expects an array!
return selectedItems[0];
// ~~~~~~
// error! Type 'string[]' is not assignable to type 'string'.
} |
Notez que TypeScript fait désormais quelque chose de similaire si l'on utilise des types d'accès indexés. Au lieu d'un type conditionnel, il est possible d'utiliser un type qui agit comme un plan de SelectionKind vers le type de retour que l'on souhaite :
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | interface QuickPickReturn {
[SelectionKind.Single]: string;
[SelectionKind.Multiple]: string[];
}
async function showQuickPick<S extends SelectionKind>(
prompt: string,
selectionKind: S,
items: readonly string[],
): Promise<QuickPickReturn[S]> {
// ...
} |
Pour de nombreux utilisateurs, il s'agira d'une manière plus...
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.