TypeScript 5.4 (liberada 6 de marzo de 2024) es una release incremental con algunos añadidos muy prácticos. Los más importantes: el nuevo NoInfer utility type que resuelve problemas comunes de inferencia, narrowing mejorado en closures, y soporte nativo de Object.groupBy y Map.groupBy. Este artículo cubre qué trae, con ejemplos reales, y cómo actualizar sin drama.
NoInfer: resolver inferencia confusa
El problema clásico:
function createStreetLight<C extends string>(
colors: C[],
defaultColor?: C
) {}
createStreetLight(["red", "yellow", "green"], "blue");
// TS5.3: no error, infiere C como "red" | "yellow" | "green" | "blue"
TS infiere C de ambos parámetros. El programador probablemente quería que solo colors contribuya a la inferencia y defaultColor se restrinja.
Con 5.4:
function createStreetLight<C extends string>(
colors: C[],
defaultColor?: NoInfer<C>
) {}
createStreetLight(["red", "yellow", "green"], "blue");
// Error: Argument of type '"blue"' is not assignable to parameter of type '"red" | "yellow" | "green"'
NoInfer<T> excluye el parámetro de la inferencia. Adiós a workarounds con conditional types complejos para esto.
Narrowing en closures preservado
Un caso que históricamente ha mordido:
function foo(arr: (string | number)[]) {
if (arr.every(x => typeof x === 'string')) {
// arr.forEach en callback: ¿TS sabe que arr es string[]?
arr.forEach(x => {
x.toUpperCase(); // pre 5.4: error
});
}
}
Antes de 5.4, el callback perdía el narrowing. Tenías que capturar el narrowed array o usar casts.
En 5.4, el narrowing se preserva dentro de callbacks que se ejecutan de forma síncrona. Menos as as, menos type guards manuales.
Object.groupBy y Map.groupBy
Stage 3 de ES en su momento, ahora tipados en TS:
const nums = [1, 2, 3, 4, 5];
// Agrupar por paridad
const grouped = Object.groupBy(nums, n =>
n % 2 === 0 ? "even" : "odd"
);
// grouped: Partial<Record<"even" | "odd", number[]>>
Conveniencia bienvenida sin depender de lodash.
Mejora de rendimiento del compilador
5.4 incluye varias mejoras internas:
- Menor uso de memoria en proyectos grandes.
- Builds incrementales más rápidos.
- Mejor deduplication de type instances.
En monorepos grandes, notarás. En proyectos de 10 archivos, imperceptible.
Cambios que pueden romper
Pocos y menores:
- Mapped type variations con homomorfismo más estricto — puede pillar edge cases en librerías con type gymnastics.
- Algunas signatures de
Function.prototype.bindmás estrictas.
En proyectos de aplicación, la mayoría no notará.
Para librerías de tipos
Si mantienes librerías con APIs genéricas, 5.4 abre patrones:
// Antes: workaround con intersection trick
declare function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>;
// Con NoInfer, casos más sofisticados son expresables directamente
declare function configure<Options>(
defaults: Options,
overrides: NoInfer<Partial<Options>>
): Options;
Los autores de Zod, tRPC, React Query están aprovechando estos en sus v4/v5.
Actualizar desde 5.3
Pasos típicos:
npm install -D typescript@5.4npx tsc --noEmit— ver errores nuevos.- Para proyectos grandes,
--incrementalayuda. - Actualizar
@types/*paquetes si alguno es relevante. - Si usas ESLint con TS, actualizar
@typescript-eslint/parsera versión compatible.
En 90% de casos, sin trabajo extra.
Patrones nuevos que habilita
Tipar un “default value” sin polucionar inferencia
function withDefault<T>(
value: T,
fallback: NoInfer<T>
): T {
return value ?? fallback;
}
withDefault(42, 0); // T = number
withDefault("hi", "default"); // T = string
withDefault("hi", 42); // Error
Antes requería dos type parameters. Ahora uno.
Function overloads simplificados
interface API {
request<T>(url: string, options?: RequestOptions<NoInfer<T>>): Promise<T>;
}
Cuando el tipo de T se debe deducir del return esperado pero options tiene callbacks que usan T.
Builders type-safe
class QueryBuilder<T> {
where(cb: (x: NoInfer<T>) => boolean): this;
}
El callback respeta T sin contaminar inferencia.
Qué viene después (5.5, 5.6)
Basado en roadmap público:
- Iterator helpers en el runtime JS, reflejados en TS.
- Using declarations (resource management) estabilizándose.
- Set methods proposal — intersection, union, difference.
- Mejoras continuas de rendimiento.
5.4 es incremental; las grandes features llegan en versiones siguientes.
Ecosystem readiness
Al día de la release:
- ESLint / prettier: compatibles.
- Vite / esbuild / swc: no requieren actualización.
- Next.js / Nuxt / SvelteKit: sin problemas.
- React / Vue / Angular: funciona.
- tRPC / Zod / React Query: algunos adoptan
NoInferen próximas releases.
Es una de las releases menos traumáticas.
Lo que no está en 5.4
Expectativas que no se cumplieron aún:
- Decorators siguen en parameter decorators (TC39 pendiente).
- Pipeline operator no existe en JS todavía.
- Pattern matching tampoco.
- Branded types como construcción de lenguaje, no aún.
Son wish-list; la comunidad los pide.
Estilo: cuando usar NoInfer
Guideline:
- Función con genérico derivado de un parámetro “principal”.
- Otros parámetros deberían usar el tipo, no contribuir a inferirlo.
Candidato claro: callbacks, defaults, configs opcionales.
Evitar si:
- Todos los parámetros son simétricos (ambos legítimamente inferidos).
- Caso simple donde un cast explícito es igual de legible.
No sobreusar — el objetivo es clarificar, no demostrar dominio de utility types.
Conclusión
TypeScript 5.4 es una release de mejoras incrementales pero algunas muy prácticas. NoInfer resuelve un patrón común que requería workarounds feos. Narrowing en closures reduce los casts molestos. Object.groupBy elimina una dependencia común. Rendimiento mejorado ayuda en monorepos. Actualizar es seguro para la mayoría, y el ecosistema se adapta rápido. Para equipos TypeScript activos, migrar pronto vale la pena — los nuevos patrones hacen el código más expresivo y menos hacky.
Síguenos en jacar.es para más sobre TypeScript, desarrollo moderno y ecosistema JavaScript.