ProgrammationDéveloppeur Frontend

Comment TypeScript fonctionne-t-il avec des user-defined type guards (fonctions de garde de type personnalisées), à quoi servent-elles et comment créer des fonctions de type predicate valides ? Quelles pièges typiques rencontrent-on lors de leur mise en œuvre ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Contexte de la question : TypeScript étend les vérifications de type normales, permettant aux développeurs de créer leurs propres fonctions – type guards – qui vérifient si un objet correspond à un certain type. Ceci est nécessaire pour travailler avec des types union, des structures dynamiques et des API où le type de valeur peut varier.

Le problème est souvent que les vérifications de type normales via typeof et instanceof sont limitées aux primitifs et aux classes, et il est impossible de définir des structures ou des types complexes sans cela. Il faut être capable d'indiquer explicitement au compilateur dans quels cas la valeur est sécurisée pour se réduire au type désiré.

La solution consiste à écrire des fonctions de garde de type sous la forme function isCat(obj: Animal): obj is Cat {...}, où le point clé est le type predicate dans le type de retour de la fonction.

Exemple de code :

interface Dog { bark: () => void; } interface Cat { meow: () => void; } type Pet = Dog | Cat; function isDog(pet: Pet): pet is Dog { return (pet as Dog).bark !== undefined; } function makeSound(pet: Pet) { if (isDog(pet)) { pet.bark(); } else { pet.meow(); } }

Caractéristiques clés :

  • La vérification de type est réalisée via des fonctions de type predicate spéciales de la forme param is Type.
  • Il est possible de créer ses propres guards et de les utiliser pour n'importe quelle structure, pas uniquement pour les objets-classe.
  • Les type guards incorrects peuvent entraîner des erreurs d'exécution et un rétrécissement incorrect du type.

Questions pièges.

Est-il suffisant de retourner true/false dans une fonction de type guard pour que le compilateur réduise le type ?

Non. Il est important d'indiquer clairement le type de retour sous la forme de type predicate (par exemple, pet is Dog), sinon TypeScript ne réduira pas automatiquement le type de la valeur, même si la fonction ne retourne que true ou false.

Peut-on utiliser un type guard à l'intérieur d'un callback (par exemple, dans filter), et la réduction fonctionnera-t-elle correctement ?

Oui, si le type guard est correctement annoté, le compilateur réduira le type des éléments du tableau après filter et à l'intérieur des fonctions forEach/callback. Mais si l'annotation est absente ou incorrecte, le résultat aura un type union au lieu d'un type spécifié.

const pets: Pet[] = [...]; const dogs = pets.filter(isDog); // TypeScript sait que dogs: Dog[]

Quelles sont les différences entre les type guards personnalisés et les vérifications de type classiques via typeof, instanceof ?

Les fonctions de type guard permettent de vérifier n'importe quelle structure, de décrire des vérifications de n'importe quel niveau de complexité, de traiter des interfaces sans classe, et pas seulement des types de base et des classes.

Erreurs typiques et anti-patterns

  • Le type guard ne renvoie pas un type predicate, mais juste un boolean — pas d'effet pour le compilateur.
  • Vérifie uniquement des clés superficielles, ne garantissant pas l'intégrité structurelle.
  • Le type guard renvoie toujours true, désactivant effectivement la protection de type.
  • Le paramètre d'entrée est trop général, ou un cast implicite est effectué sans validation.

Exemples concrets

Cas négatif

La fonction filtre les utilisateurs en faisant un type guard sans type predicate :

function isValidAdmin(user: any): boolean { return user.isAdmin === true; } const admins = users.filter(isValidAdmin); // admins: any[]

Avantages :

  • Rapide, l'effet est visible au runtime

Inconvénients :

  • Après le filtrage, le type de données n'est pas précisé pour TypeScript, des erreurs peuvent survenir lors de l'accès aux propriétés

Cas positif

La fonction filtre avec un type predicate correct :

interface Admin { name: string; isAdmin: true; } function isAdmin(user: any): user is Admin { return user && user.isAdmin === true; } const admins = users.filter(isAdmin); // admins: Admin[]

Avantages :

  • Le type des résultats est connu avec précision
  • Réduit le risque d'erreurs lors de l'utilisation ultérieure du résultat

Inconvénients :

  • Nécessite un peu plus d'attention à la signature et à la couverture des vérifications