La reducción de tipo (Type Narrowing) en TypeScript es el proceso mediante el cual el compilador "entiende" que en una determinada sección del código, una variable tiene un tipo más específico, basado en ciertas condiciones.
Técnicas comunes de reducción:
typeof:function example(x: number | string) { if (typeof x === 'string') { // x: string aquí return x.toUpperCase(); } else { // x: number aquí return x.toFixed(2); } }
instanceof (para clases):if (dateObj instanceof Date) { // dateObj: Date }
null y undefined:function print(value?: string) { if (value != null) { // value: string console.log(value.length); } }
type Pet = { kind: 'dog'; woof: () => void } | { kind: 'cat'; meow: () => void }; function sound(pet: Pet) { if (pet.kind === 'dog') { pet.woof(); } else { pet.meow(); } }
TypeScript también admite funciones de predicado personalizadas:
function isString(x: unknown): x is string { return typeof x === 'string'; }
La reducción hace que las comprobaciones de tipos sean más seguras y el código más fiable.
¿Se puede garantizar la reducción de tipo a través de comparaciones ordinarias (por ejemplo,
==/===), y siempre funciona?
Respuesta: No. No siempre TypeScript entiende el tipo a partir de comparaciones simples, especialmente si la comparación es demasiado "vaga" o se realiza a través de variables/properties indirectas. Para la reducción, a menudo es necesario utilizar mecánicas explícitas (typeof, instanceof, propiedades discriminantes y type guards).
Ejemplo:
function foo(x: number | string | null) { if (x) { // x: string | number, null ya no puede existir, pero no habrá reducción a uno concreto } }
Historia
En un gran proyecto de TypeScript, la condición user.role == 'admin' no redujo el tipo de datos, y aún así se necesitaban verificaciones para la existencia de la propiedad. Los desarrolladores subestimaron las reglas de reducción, lo que condujo a errores "Cannot read property ... of undefined".
Historia
En una aplicación móvil, la función aceptaba un objeto o una cadena. A través de una llamada indirecta a una función que cambiaba el tipo, no hubo reducción, y en algunos dispositivos ocurrió un fallo al llamar a un método que no existía en una cadena. Las pruebas fallaron en casos raros.
Historia
Al migrar el código de JavaScript a TypeScript, no implementaron sus funciones de type guard, asumiendo que las verificaciones de propiedades siempre reducirían el tipo. Como resultado, objetos complejos con campos opcionales se comportaron de manera incorrecta, y en tiempo de ejecución surgieron errores de acceso a datos impredecibles.