Metaprogramação em TypeScript: como escrever código que escreve código

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: a magia que está oculta nos tipos

\n\n

Imagine que você não está apenas escrevendo código, mas um conjunto de instruções para o próprio compilador, que com base nas suas regras gera construções seguras e autodocumentadas. Isso é a metaprogramação em TypeScript — a capacidade de escrever código que opera no nível dos tipos, analisa e transforma outros tipos durante a compilação.

\n\n

Muitos desenvolvedores param nos genéricos e interfaces básicos, sem sequer suspeitar do poderoso arsenal escondido por trás das palavras-chave infer, extends e construções condicionais. Neste artigo, exploraremos técnicas avançadas de metaprogramação TypeScript que o transformarão de um simples usuário da linguagem em um verdadeiro arquiteto dela.

\n\n

1. Tipos condicionais: if no nível dos tipos

\n\n

Tipos condicionais são a base de qualquer programação em nível de tipo. Eles funcionam como um operador ternário, mas no nível do 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

Mas a verdadeira magia começa quando os combinamos com genéricos. Por exemplo, vamos criar um tipo que extrai apenas as chaves de string de um 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 exemplo mostra como tipos avançados TypeScript permitem filtrar e transformar estruturas de dados em tempo de compilação. Você está literalmente programando a lógica que é executada antes do código chegar ao runtime.

\n\n

2. Infer: extraindo tipos de outros tipos

\n\n

A palavra-chave infer é, talvez, a ferramenta mais poderosa no arsenal da metaprogramação TypeScript. Ela permite "olhar para dentro" de um tipo e extrair suas partes constituintes. Vejamos um exemplo clássico — obter o tipo de retorno de uma função:

\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

Mas o infer pode fazer muito mais. Vamos extrair o tipo do primeiro argumento de uma função:

\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

E que tal uma análise recursiva de Promises? Vamos criar um tipo que "desembrulha" todos os Promises aninhados:

\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

Observe a recursão — esta é uma das técnicas avançadas TypeScript mais elegantes, que permite lidar com estruturas infinitamente aninhadas.

\n\n

3. Mapped types: transformação de objetos

\n\n

Mapped types permitem criar novos tipos com base em tipos existentes, iterando sobre suas chaves. É uma ferramenta indispensável para criar utilitários genéricos:

\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

Mas podemos ir além. Vamos criar um tipo que torna todas as propriedades opcionais, mas apenas se forem strings:

\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