Saltar al contenido principal
Volver atrás

TypeScript #6: Type Guards & Narrowing

#typescript #narrowing #basics

Aprende a decirle a TypeScript "confía en mí, sé lo que hago". Type Guards, operador 'is' y narrowing.

TypeScript es muy listo, pero a veces necesita “pistas” para saber exactamente de qué tipo es una variable en un momento dado. Este proceso de refinar un tipo amplio (como string | number) a uno más específico (como string) se llama Narrowing.

Y para lograrlo, usamos Type Guards (Guardias de Tipos).

1. El problema: La incertidumbre

Imagina que tienes una función que acepta un ID, pero este ID puede ser un número (Base de datos SQL) o un texto (UUID).

typescript
function printId(id: string | number) {
// Error: Property 'toUpperCase' does not exist on type 'string | number'.
// Property 'toUpperCase' does not exist on type 'number'.
console.log(id.toUpperCase()); 
}

printId("abc");

TypeScript se queja porque no está seguro de que id sea un string. Si fuera un número, .toUpperCase() explotaría en tiempo de ejecución. 💥

2. Solución: typeof Guards

La forma más común de narrowing es usar el operador typeof de JavaScript dentro de un condicional if. TypeScript entiende este código y “reduce” el tipo dentro del bloque.

typescript
function printId(id: string | number) {
if (typeof id === "string") {
  // ¡Aquí dentro TypeScript SABE que id es string!
  console.log("Tu ID es: " + id.toUpperCase());
} else {
  // Y aquí sabe que TIENE que ser number
  console.log("Tu ID es: " + id.toFixed(2));
}
}

printId("Pizza!");
printId(123.456);
Tipos soportados por typeof

Recuerda que typeof solo funciona con primitivos básicos: "string", "number", "boolean", "symbol", "undefined", "object" y "function". ¡Ojo! typeof null devuelve "object", lo cual es un bug histórico de JS.

3. Truthiness Narrowing

A veces no necesitas saber el tipo exacto, solo si el valor existe (no es null ni undefined).

typescript
function printName(name?: string) {
// name es string | undefined
if (name) {
  // Aquí name es string (porque undefined es falsy)
  console.log(name.toUpperCase());
}
}

4. instanceof Narrowing

Para objetos construidos con clases, typeof no sirve de mucho (todo es “object”). Aquí entra instanceof.

typescript
class Perro {
ladrar() { return "Guau!"; }
}

class Gato {
maullar() { return "Miau!"; }
}

function comunicar(mascota: Perro | Gato) {
if (mascota instanceof Perro) {
  console.log(mascota.ladrar());
} else {
  console.log(mascota.maullar());
}
}

comunicar(new Perro());

5. Predicados de Tipo (Custom Type Guards)

¿Qué pasa si quieres reutilizar una lógica de comprobación compleja? Puedes crear tus propias funciones de guardia usando el operador is.

La sintaxis clave es el tipo de retorno: param is Type.

typescript
type Pez = { nadar: () => void };
type Pajaro = { volar: () => void };

// Función guardia personalizada
function esPez(mascota: Pez | Pajaro): mascota is Pez {
return (mascota as Pez).nadar !== undefined;
}

function mover(animal: Pez | Pajaro) {
if (esPez(animal)) {
  // TypeScript confía en tu función: aquí es Pez
  animal.nadar();
} else {
  // Aquí debe ser Pajaro
  animal.volar();
}
}

const nemo = { nadar: () => console.log("Nadando...") };
mover(nemo);
Poder y Responsabilidad

Cuando usas is, le dices al compilador “confía en mí”. Si tu lógica dentro de la función esPez está mal, TypeScript se lo creerá y podrías tener errores en tiempo de ejecución.

6. Ejercicio: El Formateador Universal

¡Te toca! Tengo una función que recibe un input. Puede ser un string o un number.

  • Si es string: Devuélvelo en MAYÚSCULAS.
  • Si es number: Devuélvelo con 2 decimales (usa .toFixed(2)).

El Narrowing es esencial para escribir código que sea flexible (acepte varios tipos) pero seguro (trate cada uno como corresponde). En el siguiente capítulo, veremos qué pasa cuando realmente no sabemos qué tipo nos llega… unknown vs any.