Microsoft annonce la disponibilité de TypeScript 2.8 qui s'accompagne des types conditionnels,
et de contrôle granulaire sur les modificateurs de type mappés

Microsoft a annoncé la disponibilité de TypeScript 2.8.

Types conditionnels

Les types conditionnels sont une nouvelle construction dans TypeScript qui permette de choisir des types basés sur d'autres types. Ils prennent la forme

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
A extends B ? C : D

où A, B, C et D sont tous les types. Vous devriez lire cela comme étant « quand le type A est assignable à B, alors ce type est C; sinon, c'est D ». Ceux qui ont déjà utilisé la syntaxe conditionnelle dans d’autres langages comme JavaScript vont probablement très vite retrouver leurs repères.

Prenons un exemple spécifique:

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
interface Animal {
    live(): void;
}
interface Dog extends Animal {
    woof(): void;
}
 
// Has type 'number'
type Foo = Dog extends Animal ? number : string;
 
// Has type 'string'
type Bar = RegExp extends Dog ? number : string;

Vous pourriez vous demander pourquoi cela est immédiatement utile. Nous pouvons dire que Foo sera un nombre, et Bar sera une chaîne, donc nous pourrions aussi bien l'écrire explicitement. Mais le vrai pouvoir des types conditionnels vient de leur utilisation avec des génériques.

Par exemple, prenons la fonction suivante :

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
interface Id { id: number, /* other fields */ }
interface Name { name: string, /* other fields */ }
 
declare function createLabel(id: number): Id;
declare function createLabel(name: string): Name;
declare function createLabel(name: string | number): Id | Name;

Ces surcharges pour createLabel décrivent une seule fonction JavaScript qui fait un choix en fonction des types de ses entrées. Notez deux choses :
  1. Si une bibliothèque doit faire le même type de choix encore et encore tout au long de son API, cela devient fastidieux ;
  2. Nous devons créer trois surcharges : une pour chaque cas lorsque nous sommes sûrs du type, et une pour le cas le plus général. Pour tous les autres cas que nous aurions à traiter, le nombre de surcharges augmenterait de façon exponentielle.

Au lieu de cela, nous pouvons utiliser un type conditionnel pour réduire à la fois nos surcharges et créer un alias de type afin que nous puissions réutiliser cette logique.

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
type IdOrName<T extends number | string> =
    T extends number ? Id : Name;
 
declare function createLabel<T extends number | string>(idOrName: T):
    T extends number ? Id : Name;
 
let a = createLabel("typescript");   // Name
let b = createLabel(2.8);            // Id
let c = createLabel("" as any);      // Id | Name
let d = createLabel("" as never);    // never
Tout comme la façon dont JavaScript peut prendre des décisions au moment de l'exécution en fonction des caractéristiques d'une valeur, les types conditionnels permettent à TypeScript de prendre des décisions dans le système de types en fonction des caractéristiques des autres types.

Distribution sur les unions avec les types conditionnels

Lorsque les types conditionnels agissent sur un seul paramètre de type, ils se répartissent entre les unions. Donc, dans l'exemple suivant, Bar a le type string [] | number [] car [C]Foo[/CB] est appliqué à l’union type string | number
Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
type Foo<T> = T extends any ? T[] : never;
 
/**
 * Foo distributes on 'string | number' to the type
 *
 *    (string extends any ? string[] : never) |
 *    (number extends any ? number[] : never)
 * 
 * which boils down to
 *
 *    string[] | number[]
 */
type Bar = Foo<string | number>;

Au cas où vous deviez éviter de distribuer sur des unions, vous pouvez entourer chaque côté du mot-clé extends avec des crochets :

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
type Foo<T> = [T] extends [any] ? T[] : never;
 
// Boils down to Array<string | number>
type Bar = Foo<string | number>;;


Contrôle granulaire sur les modificateurs de type mappés

Les types d'objets mappés de TypeScript sont une construction incroyablement puissante. Une fonctionnalité pratique est qu'ils permettent aux utilisateurs de créer de nouveaux types avec des modificateurs définis pour toutes leurs propriétés. Par exemple, le type suivant crée un nouveau type basé sur T et où chaque propriété dans T devient readonly et optionnel (?).

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
// Creates a type with all the properties in T,
// but marked both readonly and optional.
type ReadonlyAndPartial<T> = {
    readonly [P in keyof T]?: T[P]
}

Les types d'objets mappés peuvent donc ajouter des modificateurs, mais jusqu'à présent, il n'y avait aucun moyen de supprimer les modificateurs de T.

TypeScript 2.8 fournit une nouvelle syntaxe pour supprimer les modificateurs dans les types mappés avec l'opérateur -, et une nouvelle syntaxe plus explicite pour ajouter des modificateurs avec l'opérateur +. Par exemple :

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
type Mutable<T> = {
    -readonly [P in keyof T]: T[P]
}
 
interface Foo {
    readonly abc: number;
    def?: string;
}
 
// 'abc' is no longer read-only, but 'def' is still optional.
type TotallyMutableFoo = Mutable<Foo>

Dans ce qui précède, Mutable supprime readonly de chaque propriété du type sur lequel elle est mappée.

De même, TypeScript fournit maintenant un nouveau type requis dans lib.d.ts qui supprime l'optionalité de chaque propriété :

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
/**
 * Make all properties in T required
 */
type Required<T> = {
    [P in keyof T]-?: T[P];
}

L'opérateur + peut être utile lorsque vous voulez rappeler qu'un type mappé ajoute des modificateurs. Par exemple, notre ReadonlyAndPartial de ci-dessus pourrait être défini comme suit :

Code TypeScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
type ReadonlyAndPartial<T> = {
    +readonly [P in keyof T]+?: T[P];
}

Source : Microsoft

Et vous ?

Quels sont les changements qui vous intéressent le plus ?