CodeForge

TypeScript de Cero a Experto / Narrowing

instanceof y el operador in: estrechar objetos

Teoría18 min20 XP

typeof distingue primitivos, pero tus datos también son objetos y clases —y ahí no sirve (typeof de cualquier objeto es solo "object"). Para estrechar entre objetos tienes dos herramientas: instanceof (¿viene de esta clase?) y el operador in (¿tiene esta propiedad?). Con ellas, TS razona sobre la forma de tus objetos.

Dos formas de identificar un objeto

instanceof: ¿de qué clase viene?

Para objetos creados con new (clases del módulo 11 de JS, o Error, Date, Array), instanceof estrecha al tipo de esa clase:

instanceof.ts
class GastoError extends Error {
constructor(public campo: string) {
  super("Gasto inválido: " + campo);
}
}

function manejar(error: unknown): string {
// el catch de JS da 'unknown' (o 'any'): estrechamos con instanceof
if (error instanceof GastoError) {
  // 🔍 aquí error es GastoError → tiene .campo Y .message
  return "Campo malo: " + error.campo;
}
if (error instanceof Error) {
  // 🔍 aquí error es Error → tiene .message
  return "Error: " + error.message;
}
return "Error desconocido";
}

instanceof es EL patrón para manejar errores tipados (recuerda el módulo 15 de JS): el catch te da un unknown, y instanceof lo estrecha a tu clase de error —dándote acceso seguro a sus campos extra. Funciona con cualquier cosa creada por una clase: tus errores, Date, Map, RegExp. La pregunta que responde: "¿este objeto nació de este molde?".

El operador in: ¿tiene esta propiedad?

Cuando tus objetos NO son instancias de clases sino objetos planos (lo más común en JS/TS moderno), los distingues por sus PROPIEDADES con in:

in.ts
type Circulo = { radio: number };
type Rectangulo = { ancho: number; alto: number };
type Figura = Circulo | Rectangulo;

function area(figura: Figura): number {
if ("radio" in figura) {
  // 🔍 tiene 'radio' → TS estrecha a Circulo
  return Math.PI * figura.radio ** 2;
}
// 🔍 no tiene radio → por descarte, es Rectangulo
return figura.ancho * figura.alto;
}

console.log(area({ radio: 5 }).toFixed(2));
console.log(area({ ancho: 4, alto: 3 }));

"radio" in figura pregunta si el objeto tiene esa propiedad, y TS lo usa para estrechar: dentro del if, figura es un Circulo (el único con radio). Es perfecto para objetos planos que se distinguen por qué campos tienen. Funciona, pero fíjate que depende de recordar QUÉ campo es único de cada tipo —en la lección 4 verás una forma aún mejor y más robusta: las discriminated unions.

Tienes type Figura = { radio: number } | { lado: number } y quieres calcular el área. ¿Cuál estrecha correctamente entre las dos?

Mini-reto

Estrecha objetos: 1) crea type Perro = { ladra: boolean } y type Gato = { maulla: boolean }, y type Mascota = Perro | Gato; 2) escribe sonido(m: Mascota): string que use "ladra" in m para devolver "Guau" o "Miau"; 3) bonus: escribe una función que reciba unknown, y con instanceof Date devuelva la fecha formateada o "no es fecha". Sientes la diferencia entre preguntar por el campo (in) y por el molde (instanceof).

Qué sigue

Los guardias que viste son los que TS trae de fábrica. Pero puedes crear los TUYOS: funciones que le enseñan a TypeScript "si esto devuelve true, entonces el valor es de tal tipo". Se llaman type guards personalizados, y son la llave para validar datos del mundo exterior con seguridad total.