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 |
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 |
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 |
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 :
- Si une bibliothèque doit faire le même type de choix encore et encore tout au long de son API, cela devient fastidieux ;
- 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 ?