Los generics son la herramienta más potente de TypeScript para escribir código reutilizable con tipos precisos. La mayoría de los desarrolladores los conocen en su forma básica <T> pero el nivel avanzado incluye constraints con extends keyof, parámetros con valor por defecto, variadic tuple types para manipular tuplas de longitud arbitraria, y el patrón de Higher-Kinded Types (HKT) para simular tipos de orden superior. Esta guía explora cada uno con ejemplos aplicados.
Constraints: extends y extends keyof
// Constraint básico: T debe tener la propiedad length
function masLargo<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
masLargo("hola", "mundo"); // "mundo"
masLargo([1, 2], [3]); // [1, 2]
// extends keyof: K debe ser una clave de T
function obtenerPropiedad<T, K extends keyof T>(obj: T, clave: K): T[K] {
return obj[clave];
}
const usuario = { id: 1, nombre: "Ana", activo: true };
const nombre = obtenerPropiedad(usuario, "nombre"); // string
// obtenerPropiedad(usuario, "foo"); // Error: "foo" no es keyof typeof usuario
Parámetros de tipo con valor por defecto
// Sin el default hay que indicar T siempre
type Respuesta<T = unknown> = {
datos: T;
ok: boolean;
codigo: number;
};
type RespuestaSimple = Respuesta; // Respuesta<unknown>
type RespuestaUsuario = Respuesta<{ id: number; nombre: string }>;
// Default condicionado al tipo anterior:
type PaginaResultados<T, E = Error> = {
items: T[];
error?: E;
total: number;
};
Variadic tuple types
TypeScript 4.0 introdujo la posibilidad de usar ...T dentro de tuplas cuando T es un array o tupla, lo que permite escribir tipos para operaciones como concat, zip o currying con tipos exactos:
// Concatenar dos tuplas preservando tipos exactos: type Concat<A extends unknown[], B extends unknown[]> = [...A, ...B]; type T1 = Concat<[string, number], [boolean, Date]>; // [string, number, boolean, Date] // Head y Tail de una tupla: type Head<T extends unknown[]> = T extends [infer H, ...infer _] ? H : never; type Tail<T extends unknown[]> = T extends [infer _, ...infer R] ? R : never; type H = Head<[string, number, boolean]>; // string type Ta = Tail<[string, number, boolean]>; // [number, boolean] // Append al final: type Append<T extends unknown[], V> = [...T, V]; type T2 = Append<[string, number], boolean>; // [string, number, boolean]
Currying tipado
// Una función currificada que TypeScript puede tipar exactamente:
declare function curry<T extends unknown[], R>(
fn: (...args: T) => R
): T extends [infer A, ...infer Rest]
? (arg: A) => Rest extends [] ? R : ReturnType<typeof curry<Rest, R>>
: R;
// En la práctica se usan overloads para casos concretos:
function sumar(a: number, b: number, c: number): number {
return a + b + c;
}
// curry(sumar)(1)(2)(3) debería ser number
Tipos recursivos con profundidad limitada
// JSON seguro como tipo recursivo:
type JSONValor =
| string
| number
| boolean
| null
| JSONValor[]
| { [clave: string]: JSONValor };
// DeepReadonly (2-3 niveles con condicionales):
type DeepReadonly<T> =
T extends (infer U)[]
? ReadonlyArray<DeepReadonly<U>>
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
interface Config {
servidor: { host: string; puertos: number[] };
}
type ConfigFija = DeepReadonly<Config>;
// No puedes mutar servidor.host ni servidor.puertos ni sus elementos
Patrón URItoKind para simular HKTs
TypeScript no tiene tipos de orden superior nativos (no puedes pasar Array como argumento de tipo sin aplicarlo), pero el patrón de registro de tipos permite aproximarse:
// Registro: mapa de nombre de tipo ? tipo concreto
interface URItoKind<A> {
"Array": A[];
"Nullable": A | null;
"Promesa": Promise<A>;
}
type URIS = keyof URItoKind<unknown>;
// "Functor" genérico que funciona con cualquier constructor registrado:
type HKT<URI extends URIS, A> = URItoKind<A>[URI];
function mapear<URI extends URIS, A, B>(
uri: URI,
fa: HKT<URI, A>,
f: (a: A) => B
): HKT<URI, B> {
// implementación omitida
return null as any;
}
El nivel avanzado de los generics exige entender que TypeScript opera en el sistema de tipos, no en el runtime. Los variadic tuples, los tipos recursivos y los HKTs simulados son herramientas que hacen que la capa de tipos sea tan expresiva como el código que describe, sin añadir ningún coste en el bundle final.
Imagen: Pexels / César Gaviria
