ProgrammationDéveloppeur Frontend

Qu'est-ce que les types prédicats (Type Predicates) en TypeScript et comment aident-ils à créer des fonctions de type guard personnalisées ? Donnez des exemples de subtilités et de pièges possibles.

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Contexte de la question :

Avec le développement de TypeScript, la nécessité de déterminer de manière fiable un type plus étroit d'une variable dans des branches logiques est apparue. Les vérifications de type classiques (via typeof ou instanceof) ne sont pas toujours suffisantes, surtout si un objet a une structure ou une hiérarchie complexe. Pour améliorer la sécurité des données et la commodité, TypeScript a mis en œuvre un mécanisme de type prédicats pour créer des guards de type personnalisés.

Problème :

Les vérifications de type ordinaires à l'intérieur des fonctions ne donnent pas au compilateur d'informations sur le type de la variable dans le reste du code si l'on utilise uniquement le résultat true/false. Le compilateur ne "comprend" pas ce qui a été vérifié. Cela entraîne des erreurs implicites à l'exécution lorsque nous tentons par erreur d'accéder à des propriétés inexistantes.

Solution :

Les types prédicats (type predicates) à l'aide du type de substitution sous la forme 'param is Type' permettent au compilateur de comprendre qu'après l'appel de la vérification, on peut travailler avec ce paramètre comme avec un type spécifique. De telles fonctions augmentent la sécurité type et étendent le système de réduction des types pour toutes les tâches complexes.

Exemple de code :

interface Bird { fly(): void; feathers: boolean; } interface Fish { swim(): void; fins: number; } function isBird(animal: Bird | Fish): animal is Bird { return (animal as Bird).fly !== undefined; } const pet: Bird | Fish = ...; if (isBird(pet)) { pet.fly(); // OK : pet est maintenant un Bird } else { pet.swim(); // OK : pet est maintenant un Fish }

Caractéristiques clés :

  • Les fonctions prédicats personnalisées élargissent le mécanisme de réduction de type au-delà des opérateurs standards de guards de type ;
  • Les prédicats obligent à décrire explicitement ce que la fonction fait avec le type, augmentant ainsi la transparence du code et sa sécurité ;
  • La bonne mise en œuvre des guards de type personnalisés protège contre les erreurs lors du travail avec des types union.

Questions pièges.

Une fonction guard de type peut-elle fonctionner si l'on ne précise pas explicitement dans la signature le type de retour 'param is Type' ?

Non, si l'on ne précise pas explicitement 'param is Type' dans la signature, TypeScript ne pourra pas réduire le type dans les branches de code, malgré la valeur de retour true/false. Le compilateur ne comprendra pas qu'on peut utiliser le paramètre comme un type spécifique.

Exemple de code :

function isFish(animal: Fish | Bird): boolean { return (animal as Fish).swim !== undefined; } // Fonctionne ? if (isFish(pet)) { pet.swim(); // Erreur : La propriété 'swim' n'existe pas }

Peut-on utiliser des types prédicats pour vérifier des valeurs primitives, telles qu'une chaîne ou un nombre ?

Oui, c'est possible, mais on utilise souvent typeof et ces guards deviennent superflus. Néanmoins, rien n'empêche de réaliser un guard personnalisé :

function isString(x: unknown): x is string { return typeof x === "string"; }

Une fonction guard de type assure-t-elle une protection stricte contre les erreurs de type au moment de la compilation ?

Pas totalement. TypeScript s'appuie sur l'implémentation même de la fonction et ne peut pas vérifier la validité de la logique à l'intérieur. Si vous implémentez incorrectement la vérification, le compilateur ne détectera pas l'erreur, des problèmes surviendront à l'exécution.

function isFish(animal: Fish | Bird): animal is Fish { // Incorrect : renvoie toujours true return true; }

Erreurs typiques et anti-patterns

  • Erreur dans la logique du type prédicat : erreur ou faute de frappe dans le prédicat prive de la sécurité de type ;
  • Casting superflu avec as à l'intérieur du guard de type ;
  • Utilisation des types prédicats là où il serait plus simple d'utiliser les types standards typeof/instanceof.

Exemple concret

** Cas négatif Un développeur a mis en œuvre une fonction prédicat, mais a commis une erreur dans la vérification de la structure, si bien que la fonction retournait toujours true. Le code passait la compilation, mais à l'exécution, une méthode inexistant était appelée.

Avantages :

  • Le code fonctionne avec différents types sans erreurs de compilation.

Inconvénients :

  • Les erreurs précoces ne sont pas interceptées, elles ne se révèlent qu'à l'exécution.

** Cas positif Les fonctions de type prédicat sont correctement mises en œuvre et testées par des tests unitaires sur des valeurs limites et des données erronées.

Avantages :

  • Sécurité de type maximale, facilement extensible à de nouveaux types ;
  • Réduction des bugs lors du travail avec des unions et des unions discrètes.

Inconvénients :

  • Un peu plus complexe à maintenir si la structure change rapidement ; peut entraîner une détail excessive des fonctions de guard de type.