Metaprogramación en TypeScript: cómo escribir código que escribe código

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: la magia que se esconde en los tipos

\n\n

Imagina que no solo escribes código, sino un conjunto de instrucciones para el propio compilador que, basándose en tus reglas, genera construcciones seguras y autodocumentadas. Esto es la metaprogramación en TypeScript: la capacidad de escribir código que funciona a nivel de tipos, analiza y transforma otros tipos durante la compilación.

\n\n

Muchos desarrolladores se quedan en los genéricos e interfaces básicos, sin siquiera sospechar el poderoso arsenal que se esconde detrás de las palabras clave infer, extends y las construcciones condicionales. En este artículo, analizaremos técnicas avanzadas de metaprogramación en TypeScript que te transformarán de un simple usuario del lenguaje a un verdadero arquitecto del mismo.

\n\n

1. Tipos condicionales: if a nivel de tipos

\n\n

Los tipos condicionales son la base de cualquier programación a nivel de tipos. Funcionan como un operador ternario, pero a nivel del sistema de tipos:

\n\n
type IsString<T> = T extends string ? "yes" : "no";\n\ntype Test1 = IsString<"hello">;   // "yes"\ntype Test2 = IsString<42>;        // "no"\n
\n\n

Pero la verdadera magia comienza cuando los combinamos con genéricos. Por ejemplo, creemos un tipo que extrae solo las claves de tipo string de un objeto:

\n\n
type StringKeys<T> = {\n  [K in keyof T]: T[K] extends string ? K : never\n}[keyof T];\n\ninterface User {\n  name: string;\n  age: number;\n  email: string;\n}\n\ntype UserStringKeys = StringKeys<User>; // "name" | "email"\n
\n\n

Este ejemplo muestra cómo los tipos avanzados de TypeScript permiten filtrar y transformar estructuras de datos en tiempo de compilación. Literalmente estás programando la lógica que se ejecuta antes de que el código llegue al runtime.

\n\n

2. Infer: extracción de tipos a partir de otros tipos

\n\n

La palabra clave infer es, quizás, la herramienta más poderosa en el arsenal de la metaprogramación en TypeScript. Permite "mirar dentro" de un tipo y extraer sus partes constituyentes. Veamos un ejemplo clásico: obtener el tipo de retorno de una función:

\n\n
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;\n\ntype Fn = (x: number) => string;\ntype FnReturn = ReturnType<Fn>; // string\n
\n\n

Pero infer puede hacer mucho más. Extraigamos el tipo del primer argumento de una función:

\n\n
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;\n\ntype Handler = (id: number, name: string) => void;\ntype HandlerFirstArg = FirstArg<Handler>; // number\n
\n\n

¿Y qué tal un análisis recursivo de promesas? Creemos un tipo que "desenvuelva" todas las Promises anidadas:

\n\n
type UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T;\n\ntype DeepPromise = Promise<Promise<Promise<number>>>;\ntype Unwrapped = UnwrapPromise<DeepPromise>; // number\n
\n\n

Observa la recursividad: esta es una de las técnicas avanzadas de TypeScript más elegantes, que permite manejar estructuras infinitamente anidadas.

\n\n

3. Mapped types: transformación de objetos

\n\n

Los Mapped types permiten crear nuevos tipos basados en tipos existentes, iterando sobre sus claves. Son una herramienta indispensable para crear utilidades genéricas:

\n\n
type Readonly<T> = {\n  readonly [K in keyof T]: T[K];\n};\n\ntype Partial<T> = {\n  [K in keyof T]?: T[K];\n};\n
\n\n

Pero podemos ir más allá. Creemos un tipo que haga todas las propiedades opcionales, pero solo si son de tipo string:

\n\n
type PartialStringProps<T> = {\n  [K in keyof T]: T[K] extends string ? T[K] | undefined : T[K];\n};\n\ninterface Config {\n  nam                

Blogs

Book Recommendations