infer es la herramienta que convierte los tipos condicionales de TypeScript en algo realmente potente: permite capturar partes de un tipo dentro de la cláusula extends y usarlas en la rama verdadera. Dominar sus patrones es la clave para implementar tipos de utilidad propios al nivel de los que incluye la librería estándar.
infer básico: extraer tipos de funciones
// ReturnType implementado desde cero:
type MiReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type R1 = MiReturnType<() => string>; // string
type R2 = MiReturnType<(x: number) => boolean>; // boolean
// Parameters implementado desde cero:
type MiParameters<T> = T extends (...args: infer P) => any ? P : never;
type P1 = MiParameters<(a: string, b: number) => void>; // [string, number]
// ConstructorParameters:
type MiConstructorParams<T> =
T extends abstract new (...args: infer P) => any ? P : never;
class Punto { constructor(public x: number, public y: number) {} }
type CP = MiConstructorParams<typeof Punto>; // [number, number]
Head y Tail de tuplas
Con infer y los rest elements se pueden extraer el primer elemento y el resto de una tupla:
type Head<T extends unknown[]> = T extends [infer H, ...unknown[]] ? H : never; type Tail<T extends unknown[]> = T extends [unknown, ...infer T] ? T : never; type H = Head<[string, number, boolean]>; // string type T = Tail<[string, number, boolean]>; // [number, boolean] type T2 = Tail<[string]>; // [] type T3 = Tail<[]>; // never // Último elemento de la tupla: type Last<T extends unknown[]> = T extends [...unknown[], infer L] ? L : never; type L = Last<[string, number, boolean]>; // boolean
UnpackPromise y Awaited
// Una sola capa:
type UnpackPromise<T> = T extends Promise<infer V> ? V : T;
// Recursiva (equivale al tipo Awaited de la librería estándar):
type MiAwaited<T> =
T extends null | undefined
? T
: T extends PromiseLike<infer V>
? MiAwaited<V>
: T;
type A1 = MiAwaited<Promise<string>>; // string
type A2 = MiAwaited<Promise<Promise<number>>>; // number
type A3 = MiAwaited<boolean>; // boolean
Múltiples infer en una condición
Una sola cláusula extends puede capturar varios tipos a la vez:
// Separar primer argumento y resto de los parámetros:
type PrimerArg<T> = T extends (first: infer F, ...rest: infer R) => any
? [first: F, rest: R]
: never;
type PA = PrimerArg<(x: string, y: number, z: boolean) => void>;
// [first: string, rest: [number, boolean]]
// Extraer key y value de una entrada de un objeto:
type EntryOf<T> = T extends [infer K, infer V] ? { clave: K; valor: V } : never;
type E = EntryOf<['nombre', string]>; // { clave: 'nombre'; valor: string }
infer con Template Literal Types
// Extraer el nombre de un getter:
type GetterName<T extends string> =
T extends `get${infer Nombre}` ? Nombre : never;
type G = GetterName<'getNombre'>; // 'Nombre'
type G2 = GetterName<'setNombre'>; // never
// Quitar prefijo 'on' de eventos:
type EventName<T extends string> =
T extends `on${infer E}` ? Uncapitalize<E> : never;
type Ev = EventName<'onClick'>; // 'click'
// Convertir snake_case a camelCase recursivo:
type CamelCase<S extends string> =
S extends `${infer Head}_${infer Rest}`
? `${Lowercase<Head>}${Capitalize<CamelCase<Rest>>}`
: Lowercase<S>;
type CC = CamelCase<'nombre_completo_usuario'>; // 'nombreCompletoUsuario'
Combinar infer con distribución
// Extraer el tipo de los valores de un objeto:
type ValoresDeObjeto<T> = T extends Record<string, infer V> ? V : never;
type Valores = ValoresDeObjeto<{ a: string; b: number; c: boolean }>;
// string | number | boolean
// Obtener los tipos de los elementos de un array de tuplas:
type ElementosDeTuplas<T extends unknown[][]> =
T extends Array<infer E> ? E[number] : never;
Imagen: Pexels / Pixabay
