Historia de la pregunta: TypeScript amplía las verificaciones de tipo convencionales, permitiendo a los desarrolladores crear sus propias funciones personalizadas: guards de tipo, que verifican si un objeto coincide con un cierto tipo. Esto es necesario para trabajar con tipos de unión, estructuras dinámicas y APIs donde el tipo de valor puede variar.
El problema a menudo radica en que las verificaciones típicas a través de typeof e instanceof están limitadas a primitivos y clases, mientras que las estructuras o tipos complejos no se pueden definir sin esto. Es necesario saber cómo indicar claramente al compilador en qué casos el valor se reduce de manera segura al tipo deseado.
La solución es escribir funciones protectoras del tipo de la forma function isCat(obj: Animal): obj is Cat {...}, donde el punto clave es el predicado de tipo en el tipo de retorno de la función.
Ejemplo de código:
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(); } }
Características clave:
¿Es suficiente que una función guard de tipo devuelva true/false para que el compilador reduzca el tipo?
No. Es importante indicar explícitamente el tipo de retorno en forma de predicado de tipo (por ejemplo, pet is Dog), de lo contrario, TypeScript no reducirá automáticamente el tipo del valor, incluso si la función solo devuelve true o false.
¿Se puede usar un guard de tipo dentro de un callback (por ejemplo, en filter), y funcionará correctamente la reducción?
Sí, si el guard de tipo está correctamente anotado, el compilador reducirá el tipo de los elementos del array después de filter y dentro de forEach/callbacks. Pero si la anotación está ausente o es incorrecta, el resultado tendrá un tipo de unión en lugar de un tipo específico.
const pets: Pet[] = [...]; const dogs = pets.filter(isDog); // TypeScript sabe que dogs: Dog[]
¿En qué se diferencian los guards de tipo personalizados de las verificaciones de tipo convencionales a través de typeof, instanceof?
Las funciones guard de tipo permiten implementar verificaciones para cualquier estructura, describir verificaciones de cualquier complejidad, operar con interfaces sin clase, y no solo con tipos y clases básicos.
La función filtra usuarios, haciendo un guard de tipo sin un predicado de tipo:
function isValidAdmin(user: any): boolean { return user.isAdmin === true; } const admins = users.filter(isValidAdmin); // admins: any[]
Pros:
Contras:
La función filtra con un predicado de tipo correcto:
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[]
Pros:
Contras: