En TypeScript, cuando queremos verificar el tipo de una variable, contamos con herramientas que nos permiten hacer comprobaciones en tiempo de ejecución para asegurarnos de que estamos trabajando con el tipo correcto. Dos de las técnicas más importantes para esto son el uso de la palabra clave instanceof y las funciones conocidas como guardas de tipo o type guards.
Primero, hablemos de instanceof. Esta palabra clave proviene de JavaScript y nos permite comprobar si un objeto ha sido creado a partir de un constructor o clase específica. Por ejemplo, si tenemos una variable que es una instancia de la clase Date, podemos usar fecha instanceof Date para verificarlo. Esto funciona porque en JavaScript, las clases se implementan mediante funciones constructoras y prototipos, lo que permite esta comprobación en tiempo de ejecución.
Sin embargo, instanceof tiene una limitación importante: no funciona con interfaces. Las interfaces en TypeScript son un concepto que solo existe en tiempo de compilación y no se traduce a código JavaScript. Por lo tanto, no hay forma de comprobar en tiempo de ejecución si un objeto implementa una interfaz usando instanceof. Esto significa que instanceof solo es útil para clases y no para interfaces.
Entonces, ¿cómo podemos verificar tipos cuando trabajamos con interfaces? Aquí es donde entran en juego las guardas de tipo. Una guarda de tipo es una función que devuelve un valor booleano y que tiene una firma especial que indica a TypeScript que, si la función devuelve true, el parámetro es de un tipo específico. Esto se logra con la sintaxis parametro is Tipo.
Por ejemplo, imaginemos que tenemos una interfaz Cuadrado con ciertas propiedades como lado y un método pintar. Podemos crear una función guarda que reciba un parámetro de tipo any y que compruebe si ese objeto tiene las propiedades y métodos que esperamos de un Cuadrado. Si la función devuelve true, TypeScript entenderá que dentro del bloque donde se llamó a la función, el parámetro es un Cuadrado, y nos permitirá acceder a sus propiedades sin necesidad de hacer un casteo manual.
Este enfoque es especialmente útil cuando recibimos datos de tipo any o cuando no podemos usar instanceof porque estamos trabajando con interfaces. La guarda de tipo nos permite hacer comprobaciones en tiempo de ejecución basadas en la estructura del objeto, lo que se conoce como duck typing.
Veamos un ejemplo práctico de una guarda de tipo para un Cuadrado:
interface Cuadrado {
lado: number;
pintar(): void;
}
function esCuadrado(x: any): x is Cuadrado {
return x !== null &&
typeof x === 'object' &&
'lado' in x &&
typeof x.lado === 'number' &&
'pintar' in x &&
typeof x.pintar === 'function';
}
En esta función, comprobamos que x no sea nulo, que sea un objeto, que tenga la propiedad lado de tipo número y que tenga un método pintar. Si todas estas condiciones se cumplen, la función devuelve true y TypeScript sabe que x es un Cuadrado.
Luego, al usar esta función, podemos hacer algo así:
const figura: any = obtenerFigura();
if (esCuadrado(figura)) {
// Aquí TypeScript sabe que figura es un Cuadrado
figura.pintar();
console.log(`El lado mide ${figura.lado}`);
}
De esta forma, evitamos tener que hacer casteos manuales y ganamos seguridad en el tipo de datos con los que trabajamos.
Además, podemos crear guardas similares para otras interfaces, como Triangulo, comprobando las propiedades específicas que definen ese tipo. Esto nos permite manejar tipos compuestos y discriminados de manera más segura y elegante.
En resumen, mientras que instanceof es útil para comprobar tipos basados en clases, las guardas de tipo nos ofrecen una forma avanzada y flexible de verificar tipos basados en interfaces, haciendo comprobaciones en tiempo de ejecución que enriquecen la experiencia de tipado en TypeScript.