Infer: extraindo tipos dentro de conditional types
1. Introdução ao infer e Conditional Types
Os conditional types são uma das features mais poderosas do sistema de tipos do TypeScript. Sua sintaxe básica segue o padrão T extends U ? X : Y, onde avaliamos se um tipo T é atribuível a U e, dependendo do resultado, retornamos X ou Y. É aqui que o operador infer entra em cena como um mecanismo de "decomposição" de tipos.
O infer permite que você declare uma variável de tipo dentro de uma cláusula extends e a utilize no ramo verdadeiro da condicional. Diferente de keyof (que extrai chaves de um objeto) ou typeof (que captura o tipo de um valor em tempo de execução), o infer opera em tempo de compilação dentro de conditional types, possibilitando a extração de partes internas de tipos complexos.
2. Sintaxe Fundamental do infer
A estrutura básica do infer é simples:
type ExtractType<T> = T extends infer R ? R : never;
Neste exemplo, infer R captura o tipo T e o expõe como R. Embora trivial, isso demonstra o posicionamento do infer dentro de uma cláusula extends. É importante notar que infer só pode ser usado em posições de tipo dentro de uma condicional — você não pode usá-lo fora desse contexto.
// Exemplo prático
type Example = ExtractType<string>; // string
type Example2 = ExtractType<number[]>; // number[]
3. Extraindo Tipos de Funções
Um dos usos mais comuns do infer é extrair tipos de funções. O TypeScript já fornece utilitários como ReturnType<T> e Parameters<T>, que são implementados internamente com infer:
// Implementação manual de ReturnType
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// Implementação manual de Parameters
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
// Uso prático
function getUser(id: number): Promise<{ name: string; age: number }> {
return Promise.resolve({ name: "Alice", age: 30 });
}
type UserReturn = MyReturnType<typeof getUser>; // Promise<{ name: string; age: number }>
type UserParams = MyParameters<typeof getUser>; // [id: number]
Para funções assíncronas ou com overloads, o infer se mostra ainda mais útil:
// Extraindo o tipo resolvido de uma Promise
type AsyncReturnType<T> = T extends (...args: any[]) => Promise<infer U> ? U : never;
type ResolvedUser = AsyncReturnType<typeof getUser>; // { name: string; age: number }
4. Extraindo Tipos de Arrays e Tuplas
O infer também é excelente para trabalhar com arrays e tuplas:
// Extraindo o tipo dos elementos de um array
type ArrayElement<T> = T extends (infer U)[] ? U : never;
type Numbers = ArrayElement<number[]>; // number
type Strings = ArrayElement<string[]>; // string
// Extraindo o primeiro elemento de uma tupla
type FirstElement<T> = T extends [infer First, ...any[]] ? First : never;
type First = FirstElement<[string, number, boolean]>; // string
// Extraindo o último elemento de uma tupla
type LastElement<T> = T extends [...any[], infer Last] ? Last : never;
type Last = LastElement<[string, number, boolean]>; // boolean
Para casos mais avançados, como arrays aninhados:
type DeepArrayElement<T> = T extends (infer U)[]
? U extends any[] ? DeepArrayElement<U> : U
: never;
type Nested = DeepArrayElement<number[][][]>; // number
5. Inferência em Tipos de Objetos e Promises
Objetos e Promises são alvos frequentes de extração com infer:
// Extraindo tipos de propriedades de objetos
type PropertyType<T, K extends keyof T> = T extends { [P in K]: infer V } ? V : never;
interface User {
id: number;
name: string;
email: string;
}
type UserName = PropertyType<User, 'name'>; // string
// Inferindo o tipo resolvido de uma Promise (similar ao Awaited nativo)
type MyAwaited<T> = T extends Promise<infer U> ? U : T;
type PromiseString = MyAwaited<Promise<string>>; // string
type DirectNumber = MyAwaited<number>; // number
// Combinação com keyof para extrair múltiplas propriedades
type ValuesOf<T> = T extends { [K in keyof T]: infer V } ? V : never;
type UserValues = ValuesOf<User>; // string | number
6. Técnicas Avançadas com infer Recursivo
O infer recursivo permite manipular tipos complexos de forma elegante:
// DeepReadonly usando infer recursivo
type DeepReadonly<T> = T extends (infer U)[]
? ReadonlyArray<DeepReadonly<U>>
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
interface NestedObject {
user: {
name: string;
address: {
street: string;
city: string;
};
};
tags: string[];
}
type ReadonlyNested = DeepReadonly<NestedObject>;
// Torna todas as propriedades e arrays readonly recursivamente
// DeepFlatten usando infer
type DeepFlatten<T> = T extends (infer U)[]
? DeepFlatten<U>
: T extends object
? { [K in keyof T]: DeepFlatten<T[K]> }
: T;
Cuidado com recursão infinita — o TypeScript impõe limites de profundidade para evitar travamentos no compilador.
7. Padrões e Boas Práticas com infer
Ao usar infer, considere estas práticas:
// Prefira utilitários nativos quando possível
type Return = ReturnType<typeof someFunction>; // Melhor que implementar manualmente
// Nomeie variáveis de inferência de forma descritiva
type ExtractConfig<T> = T extends { config: infer ConfigType } ? ConfigType : never;
// Evite inferência ambígua
type Ambiguous<T> = T extends (infer U)[] | infer U ? U : never; // Pode causar comportamentos inesperados
// Trate casos de never explicitamente
type SafeReturn<T> = T extends (...args: any[]) => infer R
? R extends never ? never : R
: never;
8. Exemplos do Mundo Real e Conclusão
No mundo real, o infer brilha em bibliotecas e frameworks:
// Extraindo tipos de eventos do React
type EventHandler<T> = T extends (event: infer E) => void ? E : never;
type ClickEvent = EventHandler<(e: React.MouseEvent<HTMLButtonElement>) => void>;
// React.MouseEvent<HTMLButtonElement>
// Extraindo tipos de actions do Redux
type ExtractActionPayload<T> = T extends { type: string; payload: infer P } ? P : never;
interface AddUserAction {
type: 'ADD_USER';
payload: { id: number; name: string };
}
type AddUserPayload = ExtractActionPayload<AddUserAction>; // { id: number; name: string }
// Integração com template literal types para parsing de strings
type ExtractPathParams<T extends string> =
T extends `${infer Start}/:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractPathParams<Rest>]: string }
: T extends `${infer Start}/:${infer Param}`
? { [K in Param]: string }
: {};
type RouteParams = ExtractPathParams<'/users/:id/posts/:postId'>;
// { id: string; postId: string }
O operador infer é uma ferramenta essencial para metaprogramação de tipos em TypeScript. Ele permite decompor tipos complexos, extrair informações internas e construir abstrações poderosas que seriam impossíveis com outras abordagens. Dominar o infer eleva significativamente sua capacidade de criar tipos flexíveis e reutilizáveis, tornando seu código mais seguro e expressivo.
Referências
- TypeScript Handbook: Conditional Types — Documentação oficial sobre conditional types e o operador
infer - TypeScript: Infer Keyword Explained — Seção específica da documentação sobre inferência com
infer - TypeScript Deep Dive: Conditional Types — Guia detalhado sobre conditional types e padrões com
infer - Understanding TypeScript's infer Keyword — Artigo técnico com exemplos práticos do uso de
infer - TypeScript Playground: Infer Examples — Ambiente interativo para experimentar com
infere conditional types - Type Challenges: Infer — Repositório com desafios de tipos que utilizam extensivamente o operador
infer