ProgrammationArchitecte TypeScript

Comment fonctionnent les types conditionnels dans TypeScript ? Qu'est-ce que le comportement distributif des types conditionnels et comment cela fonctionne-t-il ? Expliquez avec des exemples.

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Les types conditionnels dans TypeScript permettent de décrire un type via un autre selon le principe T extends U ? X : Y. De tels types offrent de puissantes capacités pour construire une logique typique complexe, notamment dans les bibliothèques et les déclarations d'API.

Exemple de type conditionnel de base :

type IsString<T> = T extends string ? 'yes' : 'no'; type A = IsString<string>; // 'yes' type B = IsString<number>; // 'no'

Comportement distributif

Lorsque le type conditionnel est appliqué à un type union, TypeScript "distribue" la condition sur chaque élément de l'union séparément. C'est ce qu'on appelle le comportement distributif des types conditionnels.

Exemple :

type Foo<T> = T extends { id: number } ? string : boolean; type Result = Foo<{id: number} | {name: string}>; // string | boolean

C'est une fonctionnalité très puissante, mais qui crée de la confusion quant au résultat attendu, en particulier lors de l'utilisation de tableaux et du mappage des types.

Comment éviter le comportement distributif

Enveloppez le type dans un tuple :

type NoDistrib<T> = [T] extends [{id: number}] ? string : boolean; type Result = NoDistrib<{id: number} | {name: string}>; // boolean

Question piège.

Question : "Que se passe-t-il si on utilise un type conditionnel avec des types union sans les envelopper dans des tuples ? Le résultat est-il toujours le même que dans une logique normale ?"

Réponse : Le résultat peut être inattendu ! En raison de la distributivité, la condition s'applique à chaque membre des types union séparément. Pour comparer strictement tout le type union, il est nécessaire d'utiliser un wrapper (tuple).

Exemple :

type Test<T> = T extends string ? number : boolean; type A = Test<string | boolean>; // number | boolean, et non simplement boolean

Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet.


Histoire

Dans une bibliothèque de sérialisation, un type conditionnel a été utilisé pour vérifier la structure des données, mais ils ont oublié d'envelopper le paramètre générique dans un tuple. En conséquence, sur des types union complexes, les déclarations échouaient, et le compilateur émettait des types imprévisibles lors de l'utilisation de l'API.


Histoire

En essayant de réaliser une transformation de type pour traiter les champs des modèles, une perte partielle d'information s'est produite : du fait que la distributivité générait des unions, quelques branches de la logique ont été oubliées dans le traitement, et au final, la typisation est devenue trop permissive.


Histoire

Un développeur pensait que T extends SomeType à l'intérieur des types conditionnels se comporterait comme on l'attend pour l'ensemble de l'objet, mais il a eu lieu une "dispersions" — le compilateur a signalé une incohérence, et c'est ainsi que le chaos typé et des bugs sérieux dans la génération de la documentation automatique ont commencé.