Entendiendo el Estrechamiento de Tipos en TypeScript
Has escrito un type guard, pero TypeScript sigue quejándose de que la propiedad no existe. O has filtrado un array, pero el tipo resultante sigue siendo una unión. Estas frustraciones surgen de una brecha entre cómo crees que funciona el estrechamiento y cómo opera realmente el análisis de flujo de control de TypeScript.
Este artículo construye un modelo mental claro del estrechamiento de tipos en TypeScript: cómo el compilador rastrea los tipos a través de tu código y cuándo pierde esa información.
Puntos Clave
- El estrechamiento de tipos es el análisis de flujo de control de TypeScript que rastrea tipos a través de rutas de ejecución basándose en verificaciones en tiempo de ejecución que puede comprobar
- Los mecanismos principales de estrechamiento incluyen
typeof,instanceof, verificaciones de veracidad, verificaciones de igualdad, el operadoriny uniones discriminadas - Los type guards definidos por el usuario con predicados de tipo permiten estrechamiento personalizado, pero TypeScript no verifica la lógica de tu predicado
- El tipo
neverhabilita la verificación de exhaustividad en tiempo de compilación para detectar casos no manejados - El estrechamiento se rompe a través de límites de callbacks, reasignaciones de propiedades y patrones complejos de aliasing
Cómo Funciona el Análisis de Flujo de Control
El estrechamiento en TypeScript no es magia. Es el compilador siguiendo las rutas de ejecución de tu código y actualizando la información de tipos basándose en verificaciones en tiempo de ejecución que puede comprobar.
Cuando escribes un condicional, TypeScript analiza qué debe ser verdadero en cada rama:
function process(value: string | number) {
if (typeof value === 'string') {
// TypeScript sabe: value es string aquí
return value.toUpperCase()
}
// TypeScript sabe: value es number aquí
return value.toFixed(2)
}
El compilador ve la verificación typeof, la reconoce como una construcción de estrechamiento y refina el tipo en consecuencia. Esto sucede automáticamente para varios operadores de JavaScript.
Mecanismos Principales de Estrechamiento
typeof e instanceof
El operador typeof estrecha tipos primitivos de manera confiable. TypeScript entiende sus peculiaridades, incluyendo que typeof null devuelve "object".
function handle(x: unknown) {
if (typeof x === 'string') return x.length
if (typeof x === 'number') return x.toFixed(2)
}
Para instancias de clases, instanceof proporciona un estrechamiento similar:
if (error instanceof TypeError) {
console.log(error.message)
}
Veracidad e Igualdad
Las verificaciones de veracidad estrechan eliminando null y undefined:
function greet(name: string | null) {
if (name) {
return `Hello, ${name}` // name es string
}
}
Las verificaciones de igualdad estrechan a tipos literales:
type Status = 'pending' | 'complete' | 'failed'
function handle(status: Status) {
if (status === 'complete') {
// status es 'complete'
}
}
El Operador in y Uniones Discriminadas
El operador in verifica la existencia de propiedades, habilitando patrones de duck-typing:
if ('radius' in shape) {
return Math.PI * shape.radius ** 2
}
Las uniones discriminadas combinan esto con tipos literales para patrones de estrechamiento poderosos:
type Result<T> =
| { success: true; data: T }
| { success: false; error: string }
function handle<T>(result: Result<T>) {
if (result.success) {
return result.data // TypeScript sabe que data existe
}
return result.error // TypeScript sabe que error existe
}
Discover how at OpenReplay.com.
Type Guards Definidos por el Usuario
Cuando las verificaciones integradas no son suficientes, los predicados de tipo te permiten definir guards personalizados:
function isString(value: unknown): value is string {
return typeof value === 'string'
}
Una advertencia: TypeScript no verifica la lógica de tu predicado. Estás afirmando la relación; si tu verificación es incorrecta, los tipos estarán incorrectos.
TypeScript moderno (5.5+) ha mejorado aquí. Array.filter ahora puede inferir predicados de tipo automáticamente para callbacks simples, eliminando un punto de dolor común:
const mixed: (string | number)[] = ['a', 1, 'b', 2]
const strings = mixed.filter(x => typeof x === 'string')
// TypeScript infiere string[] en muchos casos
Verificación de Exhaustividad con never
El tipo never habilita la verificación de exhaustividad en tiempo de compilación:
type Circle = { kind: 'circle'; radius: number }
type Square = { kind: 'square'; side: number }
type Triangle = { kind: 'triangle'; base: number; height: number }
type Shape = Circle | Square | Triangle
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.radius ** 2
case 'square': return shape.side ** 2
case 'triangle': return 0.5 * shape.base * shape.height
default:
const _exhaustive: never = shape
return _exhaustive
}
}
Si agregas una nueva variante de forma, TypeScript genera un error en la asignación a never, obligándote a manejarlo.
Lo Que el Estrechamiento No Es
Dos construcciones a menudo confundidas con el estrechamiento merecen aclaración:
Las aserciones de tipo (as) evitan completamente el sistema de tipos. No estrechan, sobrescriben.
El operador satisfies valida que una expresión coincida con un tipo sin cambiarlo. Útil para detectar errores, pero no es un mecanismo de estrechamiento.
Cuándo el Estrechamiento Falla
El análisis de flujo de control de TypeScript tiene límites. El estrechamiento no persiste a través de:
- Límites de callbacks (aunque los closures con variables
constahora preservan el estrechamiento en TypeScript 5.4+) - Reasignaciones de propiedades entre verificaciones
- Patrones complejos de aliasing
Cuando el estrechamiento falla inesperadamente, verifica si el compilador puede realmente rastrear el flujo de control desde tu verificación hasta tu uso.
Conclusión
Piensa en el estrechamiento como TypeScript observando tus verificaciones en tiempo de ejecución y actualizando su conocimiento en consecuencia. El compilador es conservador: solo estrecha cuando puede probar que el refinamiento es sólido.
Escribe verificaciones que TypeScript pueda seguir. Prefiere uniones discriminadas sobre aserciones de tipo. Usa verificación de exhaustividad para detectar casos faltantes en tiempo de compilación en lugar de tiempo de ejecución.
El objetivo no es luchar contra el sistema de tipos, sino estructurar el código para que el estrechamiento funcione naturalmente.
Preguntas Frecuentes
El análisis de flujo de control de TypeScript no persiste el estrechamiento a través de límites de callbacks porque el callback podría ejecutarse más tarde cuando el tipo de la variable podría haber cambiado. Para solucionar esto, asigna el valor estrechado a una variable const antes del callback, o usa un type guard dentro del callback mismo.
Un type guard es una verificación en tiempo de ejecución que TypeScript reconoce y usa para estrechar tipos de manera segura. Una aserción de tipo usando 'as' le dice a TypeScript que trate un valor como un tipo específico sin ninguna verificación en tiempo de ejecución. Los type guards son más seguros porque involucran verificaciones reales, mientras que las aserciones pueden ocultar bugs si tu suposición es incorrecta.
Usa uniones discriminadas cuando tengas tipos relacionados que comparten una propiedad literal común como 'kind' o 'type'. Proporcionan estrechamiento automático en sentencias switch y habilitan la verificación de exhaustividad. Los type guards son mejores para validar datos externos o cuando no puedes modificar los tipos con los que estás trabajando.
Esto generalmente significa que TypeScript perdió el rastro de tu estrechamiento. Las causas comunes incluyen verificar una propiedad y luego acceder a ella a través de una referencia diferente, reasignar la variable entre la verificación y el uso, o que la verificación ocurra en un ámbito diferente. Mueve tu verificación de tipo más cerca de donde usas el valor, o almacena el valor estrechado en una nueva variable const.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.