Guards con typeof, instanceof e in
Los type guards son expresiones que TypeScript reconoce como comprobaciones de tipo. Cuando una condición actúa como type guard, TypeScript estrecha (narrows) el tipo de la variable dentro del bloque correspondiente, proporcionando acceso seguro a propiedades y métodos específicos.

typeof guards
El operador typeof permite distinguir entre tipos primitivos. TypeScript reconoce las comprobaciones typeof y ajusta el tipo automáticamente:
function formatear(valor: string | number | boolean): string {
if (typeof valor === "string") {
return valor.toUpperCase()
}
if (typeof valor === "number") {
return valor.toFixed(2)
}
return valor ? "verdadero" : "falso"
}
console.log(formatear("hola")) // "HOLA"
console.log(formatear(3.14159)) // "3.14"
console.log(formatear(true)) // "verdadero"
Las cadenas válidas que devuelve typeof son: "string", "number", "boolean", "undefined", "object", "function", "symbol" y "bigint":
function describir(valor: unknown): string {
if (typeof valor === "string") return `Cadena de ${valor.length} caracteres`
if (typeof valor === "number") return `Número: ${valor}`
if (typeof valor === "boolean") return `Booleano: ${valor}`
if (typeof valor === "undefined") return "Sin valor"
if (typeof valor === "function") return "Es una función"
if (typeof valor === "symbol") return "Es un simbolo"
if (typeof valor === "bigint") return `BigInt: ${valor}`
return "Es un objeto"
}
El operador
typeofdevuelve"object"tanto para objetos como paranully arrays. Para distinguir estos casos se necesitan comprobaciones adicionales comoArray.isArray()o comparación connull.
instanceof guards
El operador instanceof verifica si un objeto fue creado a partir de un constructor específico. TypeScript estrecha el tipo a la clase correspondiente:
class ErrorValidacion {
constructor(public campo: string, public mensaje: string) {}
}
class ErrorAutenticacion {
constructor(public motivo: string, public intentos: number) {}
}
class ErrorRed {
constructor(public url: string, public código: number) {}
}
type ErrorApp = ErrorValidacion | ErrorAutenticacion | ErrorRed
function manejarError(error: ErrorApp): string {
if (error instanceof ErrorValidacion) {
return `Validación fallida en '${error.campo}': ${error.mensaje}`
}
if (error instanceof ErrorAutenticacion) {
return `Autenticacion fallida: ${error.motivo} (${error.intentos} intentos)`
}
if (error instanceof ErrorRed) {
return `Error de red ${error.código} en ${error.url}`
}
return "Error desconocido"
}
const err = new ErrorValidacion("email", "Formato inválido")
console.log(manejarError(err)) // "Validación fallida en 'email': Formato inválido"
El instanceof también detecta la herencia, estrechando al tipo más específico:
class Vehiculo {
constructor(public marca: string) {}
}
class Coche extends Vehiculo {
constructor(marca: string, public puertas: number) {
super(marca)
}
}
class Moto extends Vehiculo {
constructor(marca: string, public cilindrada: number) {
super(marca)
}
}
function describir(vehiculo: Vehiculo): string {
if (vehiculo instanceof Coche) {
return `Coche ${vehiculo.marca} con ${vehiculo.puertas} puertas`
}
if (vehiculo instanceof Moto) {
return `Moto ${vehiculo.marca} de ${vehiculo.cilindrada}cc`
}
return `Vehiculo: ${vehiculo.marca}`
}
in operator guards
El operador in comprueba si una propiedad existe en un objeto. TypeScript lo usa para estrechar uniones basándose en propiedades exclusivas de cada variante:
type Articulo = {
titulo: string
contenido: string
autor: string
}
type Video = {
titulo: string
duracion: number
resolución: string
}
type Podcast = {
titulo: string
duracion: number
episodio: number
}
type Contenido = Articulo | Video | Podcast
function mostrar(contenido: Contenido): string {
if ("contenido" in contenido) {
return `Articulo: ${contenido.titulo} por ${contenido.autor}`
}
if ("resolución" in contenido) {
return `Video: ${contenido.titulo} (${contenido.resolución}, ${contenido.duracion}s)`
}
return `Podcast ep.${contenido.episodio}: ${contenido.titulo}`
}
console.log(mostrar({ titulo: "TypeScript", contenido: "...", autor: "Ana" }))
console.log(mostrar({ titulo: "Demo", duracion: 120, resolución: "1080p" }))
console.log(mostrar({ titulo: "Entrevista", duracion: 3600, episodio: 42 }))
El operador in es especialmente útil cuando no se dispone de un campo discriminante explícito y los tipos se distinguen por la presencia o ausencia de ciertas propiedades.
Equality narrowing y truthiness narrowing
Equality narrowing
TypeScript estrecha tipos mediante comparaciones de igualdad con ===, !==, == y !=:
function comparar(a: string | number, b: string | boolean): string {
if (a === b) {
// Solo string es comun a ambos tipos
// TypeScript estrecha a: string, b: string
return a.toUpperCase() + " = " + b.toUpperCase()
}
return "No son iguales o tipos diferentes"
}
La comparación con null usando == es especialmente práctica porque cubre tanto null como undefined:
function procesar(valor: string | null | undefined): string {
if (valor == null) {
// Cubre tanto null como undefined
return "Sin valor"
}
// TypeScript sabe que valor es string
return valor.trim()
}
console.log(procesar(null)) // "Sin valor"
console.log(procesar(undefined)) // "Sin valor"
console.log(procesar(" hola ")) // "hola"
La comparación con valores literales específicos también estrecha el tipo:
type Resultado = "exito" | "error" | "pendiente"
function iconoResultado(resultado: Resultado): string {
if (resultado === "exito") {
return "[OK]"
}
if (resultado === "error") {
return "[X]"
}
// TypeScript sabe que resultado es "pendiente"
return "[...]"
}
Truthiness narrowing
JavaScript trata ciertos valores como falsy: false, 0, "", null, undefined, NaN. TypeScript aprovecha las comprobaciones de truthiness para eliminar estos valores del tipo:
function saludar(nombre: string | null | undefined): string {
if (nombre) {
// Elimina null, undefined y "" (cadena vacia)
return `Hola, ${nombre}`
}
return "Hola, visitante"
}
Es útil para manejar propiedades opcionales de forma concisa:
type ConfigApp = {
titulo: string
subtitulo?: string
maxResultados?: number
debug?: boolean
}
function aplicarConfig(config: ConfigApp): string[] {
const lineas: string[] = [`Titulo: ${config.titulo}`]
if (config.subtitulo) {
lineas.push(`Subtitulo: ${config.subtitulo}`)
}
if (config.maxResultados) {
lineas.push(`Max resultados: ${config.maxResultados}`)
}
// Cuidado: config.debug podría ser false (falsy pero válido)
if (config.debug !== undefined) {
lineas.push(`Debug: ${config.debug}`)
}
return lineas
}
El truthiness narrowing puede producir errores sutiles con valores como
0,""ofalse, que son falsy pero legítimos. Para estos casos es preferible usar comprobaciones explícitas con!== undefinedo!== null.
La negación con ! filtra en la rama opuesta:
function procesarLista(items: string[] | null): number {
if (!items) {
return 0
}
// TypeScript sabe que items es string[]
return items.length
}
Type predicates y assertion functions
Type predicates con is
Un type predicate es una función que devuelve un booleano y utiliza la sintaxis parámetro is Tipo en su tipo de retorno. Cuando la función devuelve true, TypeScript estrecha el tipo del argumento:
interface Pez {
nadar(): void
nombre: string
}
interface Ave {
volar(): void
nombre: string
}
function esPez(animal: Pez | Ave): animal is Pez {
return "nadar" in animal
}
function moverAnimal(animal: Pez | Ave): string {
if (esPez(animal)) {
animal.nadar()
return `${animal.nombre} está nadando`
}
animal.volar()
return `${animal.nombre} está volando`
}
Los type predicates son especialmente útiles para validar datos de fuentes externas:
interface UsuarioAPI {
id: number
nombre: string
email: string
rol: "admin" | "usuario"
}
function esUsuarioAPI(datos: unknown): datos is UsuarioAPI {
if (typeof datos !== "object" || datos === null) return false
const obj = datos as Record<string, unknown>
return (
typeof obj.id === "number" &&
typeof obj.nombre === "string" &&
typeof obj.email === "string" &&
(obj.rol === "admin" || obj.rol === "usuario")
)
}
function procesarDatosAPI(datos: unknown): string {
if (esUsuarioAPI(datos)) {
return `Usuario ${datos.nombre} (${datos.rol}): ${datos.email}`
}
return "Datos invalidos"
}
const datosValidos = { id: 1, nombre: "Ana", email: "ana@ej.com", rol: "admin" }
const datosInvalidos = { id: "abc", nombre: 123 }
console.log(procesarDatosAPI(datosValidos)) // "Usuario Ana (admin): ana@ej.com"
console.log(procesarDatosAPI(datosInvalidos)) // "Datos invalidos"
Los type predicates también funcionan con arrays para filtrar elementos:
type Resultado = { tipo: "exito"; valor: number } | { tipo: "error"; mensaje: string }
function esExito(r: Resultado): r is { tipo: "exito"; valor: number } {
return r.tipo === "exito"
}
const resultados: Resultado[] = [
{ tipo: "exito", valor: 42 },
{ tipo: "error", mensaje: "fallo" },
{ tipo: "exito", valor: 100 },
{ tipo: "error", mensaje: "timeout" }
]
const exitosos = resultados.filter(esExito)
// Tipo: { tipo: "exito"; valor: number }[]
console.log(exitosos.map(r => r.valor)) // [42, 100]
Assertion functions con asserts
Las assertion functions son funciones que lanzan un error si la condición no se cumple. Usan la sintaxis asserts parámetro is Tipo:
function assertEsString(valor: unknown): asserts valor is string {
if (typeof valor !== "string") {
throw new Error(`Se esperaba string, se recibio ${typeof valor}`)
}
}
function assertEsNumeroPositivo(valor: unknown): asserts valor is number {
if (typeof valor !== "number" || valor <= 0) {
throw new Error(`Se esperaba un número positivo, se recibio ${valor}`)
}
}
function procesarEntrada(nombre: unknown, edad: unknown): string {
assertEsString(nombre)
assertEsNumeroPositivo(edad)
// Después de las aserciones, TypeScript sabe los tipos
return `${nombre.toUpperCase()} tiene ${edad.toFixed(0)} anios`
}
console.log(procesarEntrada("Ana", 28)) // "ANA tiene 28 anios"
// procesarEntrada(123, "abc") // Lanza Error
Las assertion functions también pueden usarse sin especificar un tipo, solo confirmando una condición:
function assertDefined<T>(valor: T | null | undefined, nombre: string): asserts valor is T {
if (valor === null || valor === undefined) {
throw new Error(`${nombre} no puede ser null o undefined`)
}
}
function obtenerConfig(clave: string): string | undefined {
const configs: Record<string, string> = { db: "postgres://localhost" }
return configs[clave]
}
const dbUrl = obtenerConfig("db")
assertDefined(dbUrl, "DB_URL")
// TypeScript sabe que dbUrl es string
console.log(dbUrl.toUpperCase())
Análisis de control de flujo
TypeScript realiza un análisis de control de flujo (control flow analysis) que rastrea el tipo de cada variable en cada punto del código. Este análisis va más allá de simples comprobaciones: interpreta retornos tempranos, asignaciones y ramificaciones:
function procesar(valor: string | number | null): string {
// Aquí: string | number | null
if (valor === null) {
return "Sin valor"
}
// Aquí: string | number (null eliminado por el return)
if (typeof valor === "number") {
return valor.toFixed(2)
}
// Aquí: string (number eliminado por el return)
return valor.toUpperCase()
}
El análisis funciona con asignaciones que cambian el tipo en función del valor asignado:
let resultado: string | number
resultado = "hola"
// TypeScript sabe que resultado es string
console.log(resultado.toUpperCase())
resultado = 42
// TypeScript sabe que resultado es number
console.log(resultado.toFixed(2))
También funciona con estructuras más complejas como bucles y operadores lógicos:
function buscarPrimero(items: (string | null)[]): string {
for (const item of items) {
if (item !== null) {
return item // TypeScript sabe que item es string
}
}
throw new Error("Ningun elemento encontrado")
}
function valorODefault(valor: string | undefined, defecto: string): string {
// El operador || no estrecha tipos, pero ?? si elimina null/undefined
return valor ?? defecto
}
El análisis de control de flujo reconoce funciones que nunca retornan (tipo never) y las usa para estrechar en las ramas restantes:
function lanzar(mensaje: string): never {
throw new Error(mensaje)
}
function obtenerValor(datos: { valor?: string }): string {
if (datos.valor !== undefined) {
return datos.valor
}
lanzar("Valor requerido")
// TypeScript sabe que está linea es inalcanzable
}
Un ejemplo completo que combina múltiples técnicas de narrowing:
type Entrada =
| { tipo: "texto"; contenido: string }
| { tipo: "número"; contenido: number }
| { tipo: "lista"; contenido: string[] }
function validarEntrada(entrada: unknown): Entrada {
if (typeof entrada !== "object" || entrada === null) {
throw new Error("Entrada debe ser un objeto")
}
if (!("tipo" in entrada) || !("contenido" in entrada)) {
throw new Error("Faltan campos requeridos")
}
const obj = entrada as { tipo: unknown; contenido: unknown }
if (obj.tipo === "texto" && typeof obj.contenido === "string") {
return { tipo: "texto", contenido: obj.contenido }
}
if (obj.tipo === "número" && typeof obj.contenido === "number") {
return { tipo: "número", contenido: obj.contenido }
}
if (obj.tipo === "lista" && Array.isArray(obj.contenido)) {
return { tipo: "lista", contenido: obj.contenido as string[] }
}
throw new Error(`Tipo no soportado: ${obj.tipo}`)
}
function procesarEntrada(entrada: Entrada): string {
switch (entrada.tipo) {
case "texto":
return `Texto: ${entrada.contenido.toUpperCase()}`
case "número":
return `Número: ${entrada.contenido.toFixed(2)}`
case "lista":
return `Lista: ${entrada.contenido.join(", ")}`
}
}
const datos = validarEntrada({ tipo: "lista", contenido: ["a", "b", "c"] })
console.log(procesarEntrada(datos)) // "Lista: a, b, c"
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en TypeScript
Documentación oficial de TypeScript
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, TypeScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de TypeScript
Explora más contenido relacionado con TypeScript y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Dominar typeof guards para tipos primitivos. Aplicar instanceof guards para jerarquías de clases. Usar el operador in para comprobar existencia de propiedades. Comprender equality narrowing y truthiness narrowing. Crear type predicates personalizados con la palabra clave is. Implementar assertion functions con asserts. Entender el análisis de control de flujo de TypeScript.