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 :
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.
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 :
Inconvénients :
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 :
Inconvénients :