Volver a TypeScript Básico

Estrechamiento de Tipos (Type Narrowing)

TypeScript estrecha los tipos en tiempo de ejecución usando guardas de tipo, haciendo el código seguro y expresivo.

Estrechamiento con typeof

function formatear(valor: string | number): string { if (typeof valor === "string") { return valor.toUpperCase(); // TypeScript sabe: string aquí } return valor.toFixed(2); // TypeScript sabe: number aquí }

Estrechamiento con instanceof

class ErrorApi extends Error { constructor(public codigoEstado: number, mensaje: string) { super(mensaje); this.name = "ErrorApi"; } } function manejarError(error: unknown): string { if (error instanceof ErrorApi) { return `Error API ${error.codigoEstado}: ${error.message}`; } if (error instanceof Error) { return error.message; } return "Error desconocido"; }

Uniones Discriminadas

// Cada caso tiene un campo literal único — el "discriminante" type Figura = | { tipo: "circulo"; radio: number } | { tipo: "rectangulo"; ancho: number; alto: number } | { tipo: "triangulo"; base: number; altura: number }; function area(figura: Figura): number { switch (figura.tipo) { case "circulo": return Math.PI * figura.radio ** 2; case "rectangulo": return figura.ancho * figura.alto; case "triangulo": return 0.5 * figura.base * figura.altura; } // TypeScript sabe que el switch es exhaustivo — no se necesita default }

Guardas de Tipo Personalizadas

interface Gato { maullar(): void } interface Perro { ladrar(): void } // Predicado de tipo: le dice a TypeScript lo que verifica la función function esGato(mascota: Gato | Perro): mascota is Gato { return "maullar" in mascota; } function hacerSonido(mascota: Gato | Perro) { if (esGato(mascota)) { mascota.maullar(); // TypeScript sabe: Gato } else { mascota.ladrar(); // TypeScript sabe: Perro } }

Funciones de Aserción

function asegurarDefinido<T>(valor: T, nombre: string): asserts valor is NonNullable<T> { if (valor === null || valor === undefined) { throw new Error(`Se esperaba que ${nombre} estuviera definido`); } } function procesarUsuario(usuario: Usuario | null) { asegurarDefinido(usuario, "usuario"); // TypeScript sabe que usuario es Usuario desde aquí console.log(usuario.nombre); }

El Operador in

interface Admin { rol: "admin"; permisos: string[] } interface Invitado { rol: "invitado"; idSesion: string } function saludarUsuario(usuario: Admin | Invitado) { if ("permisos" in usuario) { console.log(`Admin con ${usuario.permisos.length} permisos`); } else { console.log(`Sesión de invitado: ${usuario.idSesion}`); } }

Verificación de Exhaustividad

function nuncaDebeLlegar(valor: never): never { throw new Error(`Caso no manejado: ${JSON.stringify(valor)}`); } function procesar(evento: "iniciar" | "detener" | "pausar") { switch (evento) { case "iniciar": return "Iniciado"; case "detener": return "Detenido"; case "pausar": return "Pausado"; default: return nuncaDebeLlegar(evento); // Error si falta un caso } }

Buena práctica: Prefiere uniones discriminadas + switch sobre largas cadenas if/else. Añade un nuncaDebeLlegar en el default para detectar casos faltantes en tiempo de compilación.