ProgramaciónDesarrollador Frontend

¿Cómo funciona TypeScript con los guards de tipo definidos por el usuario (funciones protectoras de tipo hechas a mano), para qué sirven y cómo crear funciones de predicado de tipo correctas? ¿Cuáles son las trampas típicas que se encuentran en su implementación?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

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:

  • La verificación de tipo se implementa a través de funciones de tipo predicado especiales del tipo param is Type.
  • Se pueden crear sus propios guards y utilizarlos para cualquier estructura, no solo para objetos-clases.
  • Los guards de tipo incorrectos pueden llevar a errores en tiempo de ejecución y a la reducción incorrecta del tipo.

Preguntas capciosas.

¿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.

Errores típicos y antipatrón

  • El guard de tipo no devuelve un tipo-predicado, sino simplemente booleano; no hay efecto para el compilador.
  • Solo se verifican claves superficiales, sin garantizar la integridad estructural.
  • El guard de tipo siempre devuelve true, deshabilitando efectivamente la protección de tipo.
  • El parámetro de entrada es demasiado general, o se hace una conversión de tipo implícita sin validación.

Ejemplo de la vida

Caso negativo

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:

  • Rápido, el efecto es notable en tiempo de ejecución.

Contras:

  • Después de la filtración, el tipo de datos no se especifica para TypeScript, lo que puede llevar a errores al acceder a las propiedades.

Caso positivo

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:

  • El tipo de resultados es conocido con precisión.
  • Disminuye la probabilidad de errores al trabajar más adelante con el resultado.

Contras:

  • Requiere un poco más de atención a la firma y a la cobertura de verificaciones.