Entendendo `infer` no TypeScript
infer declara uma variável de tipo dentro da cláusula extends de um tipo condicional, capturando um subtipo correspondente para que possa ser utilizado no ramo verdadeiro — não tem significado fora dessa posição e gera um erro de compilação em qualquer outro lugar. Se você já abriu um arquivo .d.ts de uma biblioteca ou o PR de um colega e se deparou com algo como T extends (...args: any[]) => infer R ? R : any, essa linha está usando infer para extrair um fragmento de um tipo, da mesma forma que a desestruturação extrai um valor de um objeto.
Principais Pontos
infersó é válido dentro da cláusulaextendsde um tipo condicional, e a variável que ele introduz só está no escopo do ramo verdadeiro — referenciá-la no ramo falso é um erro de compilador.inferfoi introduzido no TypeScript 2.8; oinferem template literal chegou na versão 4.1,Awaited<T>na 4.5, einfer X extends Constraintna 4.7.- Quando o parâmetro de tipo é uma variável de tipo nua (naked type variable) e você passa uma união, o condicional é distribuído sobre cada membro, de modo que
inferresolve uma vez por membro e os resultados se acumulam em uma união — um bug silencioso de alargamento de tipo bastante comum. - Desde o TypeScript 4.5,
Awaited<T>é o utilitário nativo para desempacotar cadeias dePromise; não há razão para reimplementarUnwrapPromisemanualmente. - Múltiplos candidatos para a mesma variável
inferproduzem uma união em posições covariantes e uma interseção em posições contravariantes.
Tipos condicionais: o substrato onde infer vive
Um tipo condicional seleciona um entre dois tipos com base em se um tipo é atribuível a outro, usando a forma T extends X ? A : B. Funciona como um operador ternário, mas no nível de tipos: se T é atribuível a X, o tipo resolve para A; caso contrário, para B. O handbook do TypeScript sobre tipos condicionais é a referência primária.
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<42>; // false
O comportamento que costuma confundir as pessoas é a distributividade. Quando o tipo verificado é um parâmetro de tipo nu — T diretamente, sem estar encapsulado — e você passa uma união, o TypeScript distribui o condicional sobre cada membro da união separadamente e coleta os resultados em uma nova união. Encapsular o parâmetro em uma tupla ([T] extends [X]) desativa a distribuição e verifica a união como uma unidade única. Essa distinção está documentada em tipos condicionais distributivos.
type Distributed<T> = T extends string ? T[] : never;
type R1 = Distributed<string | number>; // string[] (o membro number resolve para never e é eliminado)
type NonDistributed<T> = [T] extends [string] ? T[] : never;
type R2 = NonDistributed<string | number>; // never — verificado como uma unidade
Tenha essa regra em mente: ela é a raiz da surpresa mais comum com infer, abordada na seção de armadilhas abaixo.
Anatomia de um padrão com infer
Discover how at OpenReplay.com.
Uma variável de tipo introduzida com infer só está no escopo no ramo verdadeiro do tipo condicional; referenciá-la no ramo falso é um erro de compilador do TypeScript. infer introduz a variável dentro de um padrão extends e a vincula a qualquer coisa que corresponda àquela posição quando o condicional é avaliado. Isso faz com que infer se comporte de forma semelhante à desestruturação — mas para tipos: você escreve um padrão que espelha a forma esperada, nomeia as partes que deseja extrair, e o TypeScript as preenche.
type ElementType<T> = T extends (infer U)[] ? U : T;
type A = ElementType<string[]>; // string
type B = ElementType<number>; // number
O padrão (infer U)[] diz “um array de algum tipo de elemento — chame esse tipo de elemento de U.” Quando T é string[], U é vinculado a string. Quando a correspondência falha, o ramo falso é executado, e U não está mais disponível lá:
type Bad<T> = T extends (infer U)[] ? U[] : U;
// ^ Erro: Cannot find name 'U'.
Um único padrão pode conter múltiplas variáveis infer, cada uma vinculada à sua própria posição:
type SplitFn<T> = T extends (arg: infer A) => infer R ? [A, R] : never;
type S = SplitFn<(x: number) => string>; // [number, string]
Aqui, infer A captura o tipo do parâmetro e infer R captura o tipo de retorno em uma única passagem. Essa é exatamente a técnica utilizada pelos tipos utilitários nativos.
infer na biblioteca padrão: os extratores canônicos
A biblioteca padrão do TypeScript implementa seus utilitários de extração com infer. Cada um segue a mesma estrutura: corresponder a um padrão, vincular a posição de interesse e retorná-la no ramo verdadeiro. As definições abaixo são as que estão presentes em lib/es5.d.ts.
Tipo de retorno de função — ReturnType
ReturnType<T> extrai o tipo de retorno de um tipo de função ao corresponder à forma da função e vincular a posição de retorno a infer R. A definição da biblioteca padrão restringe T para ser chamável.
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
type A = ReturnType<() => string>; // string
type B = ReturnType<() => { id: number }>; // { id: number }
Observe a restrição genérica T extends (...args: any) => any — ela faz parte da definição real da biblioteca padrão, não é uma decoração opcional.
Conclusão: ReturnType é o exemplo mais didático de infer — faça a correspondência com a função e capture o slot de retorno.
Parâmetros de função como tupla — Parameters
Parameters<T> extrai a lista de parâmetros de uma função como uma tupla, vinculando a posição do parâmetro rest a infer P.
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
type Args = Parameters<(a: string, b: number) => void>; // [a: string, b: number]
Conclusão: Como os parâmetros são modelados como um tipo de tupla, infer P sobre ...args fornece a lista completa, preservando rótulos e opcionalidade.
Resolução de Promise — use Awaited<T>, não um desempacotador manual
Desde o TypeScript 4.5, o Awaited<T> nativo desempacota recursivamente cadeias de Promise e lida com PromiseLike — não há razão para reimplementar UnwrapPromise manualmente no TypeScript moderno. Muitos tutoriais mais antigos definem seu próprio T extends Promise<infer U> ? U : T, mas essa versão de nível único não desempacota promises aninhadas nem modela objetos com .then. Awaited faz ambos, conforme descrito nas notas de lançamento do TypeScript 4.5.
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number — recursivo
type C = Awaited<boolean | Promise<number>>; // number | boolean
Conclusão: Use Awaited<T> diretamente. Escreva seu próprio desempacotador de promise baseado em infer apenas como exercício de aprendizado.
Tipo de elemento de array
Extrair o tipo de elemento de um array é o menor padrão útil com infer: corresponda a (infer U)[] e retorne U.
type ElementOf<T> = T extends readonly (infer U)[] ? U : never;
type A = ElementOf<number[]>; // number
type B = ElementOf<readonly string[]>; // string
Conclusão: Inclua readonly no padrão para que o utilitário funcione tanto para arrays mutáveis quanto para arrays somente leitura.
Cabeça, cauda e resto de tuplas
Tuplas suportam padrões posicionais com infer e elementos rest, permitindo extrair o primeiro elemento, a cauda ou o último elemento. Os padrões espelham a desestruturação de arrays do JavaScript.
type Head<T extends any[]> = T extends [infer H, ...any[]] ? H : never;
type Tail<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never;
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;
type H = Head<[1, 2, 3]>; // 1
type R = Tail<[1, 2, 3]>; // [2, 3]
type L = Last<[1, 2, 3]>; // 3
Conclusão: [infer Head, ...infer Rest] e [...any[], infer Last] são os blocos de construção para manipulação recursiva de tuplas — os mesmos padrões que alimentam roteadores no nível de tipos e bibliotecas de estado de formulários.
Inferência recursiva — Flatten
infer combinado com recursão permite que um tipo descasque estruturas arbitrariamente profundas. Flatten percorre arrays aninhados recursivamente até chegar a um tipo que não é array.
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;
type A = Flatten<number[][][]>; // number
type B = Flatten<string>; // string
Cada passagem vincula U ao tipo de elemento e o alimenta de volta em Flatten. Quando T não é mais um array, o ramo falso o retorna sem alterações.
Conclusão: infer recursivo é como você colapsa contêineres aninhados; a mesma estrutura desempacota tipos de Promise ou objetos profundamente aninhados.
Inferência em template literals — analisando strings no nível de tipos
Tipos de template literal suportam infer dentro de suas posições de substituição. O exemplo ExtractId<T> abaixo corresponde ao padrão da string e vincula o segmento variável a Id, permitindo a extração tipada de parâmetros de rota sem um parser em tempo de execução — um padrão disponível desde o TypeScript 4.1.
type ExtractId<T> = T extends `/users/${infer Id}` ? Id : never;
type A = ExtractId<"/users/42">; // "42"
type B = ExtractId<"/posts/42">; // never
Você pode encadear infer em múltiplos segmentos para analisar um caminho completo em suas partes:
type RouteParams<T> =
T extends `${infer _Start}/:${infer Param}/${infer Rest}`
? Param | RouteParams<`/${Rest}`>
: T extends `${infer _Start}/:${infer Param}`
? Param
: never;
type P = RouteParams<"/users/:userId/posts/:postId">; // "userId" | "postId"
Isso é o equivalente no nível de tipos de um parser de caminhos, e é a base do roteamento tipado em frameworks e bibliotecas que derivam objetos de parâmetros a partir de strings de rota.
Conclusão: infer em template literals transforma tipos com formato de string em dados estruturados sem nenhum custo em tempo de execução — útil para parâmetros de rota, uniões de propriedades CSS e análise de formatos de strings com marca (branded strings).
Inferência com restrição: infer X extends Constraint (TS 4.7)
O TypeScript 4.7 adicionou infer X extends Constraint, permitindo que um tipo condicional restrinja uma variável inferida no momento da correspondência — combinando inferência e verificação de restrição em uma única etapa. Isso está documentado nas notas de lançamento do TypeScript 4.7. Antes da versão 4.7, era necessário inferir primeiro e depois adicionar um segundo condicional aninhado para restringir o resultado.
// Antes do 4.7: inferir e depois verificar em um segundo condicional
type FirstStringOld<T> =
T extends [infer H, ...any[]]
? H extends string ? H : never
: never;
// 4.7+: restringir no momento da inferência
type FirstString<T> =
T extends [infer H extends string, ...any[]] ? H : never;
type A = FirstString<["hi", 1, 2]>; // "hi"
type B = FirstString<[1, 2, 3]>; // never
O TypeScript 4.8 posteriormente aprimorou a inferência com restrição para tipos primitivos como number, bigint e boolean, permitindo que o compilador preserve tipos literais mais precisos ao corresponder padrões de template string. Isso se baseia na sintaxe de infer com restrição introduzida no TypeScript 4.7, mas foi entregue como uma melhoria separada.
Conclusão: Use infer X extends C quando quiser tanto o tipo capturado quanto uma garantia sobre sua forma em uma única expressão — substitui o padrão antigo de inferir e depois aninhar.
Três armadilhas que quebram silenciosamente a inferência
As três armadilhas que quebram silenciosamente o infer são: a distributividade sobre uniões alargando resultados, a inversão de variância de múltiplos candidatos infer entre união e interseção, e padrões excessivamente específicos que caem no ramo falso. Falhas com infer costumam ser silenciosas — o tipo ainda compila, mas é mais amplo ou mais restrito do que você pretendia.
1. A distributividade alarga resultados silenciosamente
Quando o parâmetro de tipo T é uma variável de tipo nua e você passa uma união, o TypeScript distribui o condicional sobre cada membro separadamente — o que significa que infer R resolve uma vez por membro da união e os resultados são coletados em uma união, podendo alargar silenciosamente o tipo inferido além do pretendido.
type Unwrap<T> = T extends Promise<infer U> ? U : T;
// Parece retornar um único tipo; na verdade, distribui
type R = Unwrap<Promise<string> | Promise<number>>; // string | number
Se você quiser rejeitar uniões mistas em vez de colapsá-las, encapsule o parâmetro para desativar a distribuição:
type UnwrapStrict<T> = [T] extends [Promise<infer U>] ? U : T;
Conclusão: Um T nu mais uma união significa avaliação por membro. Encapsule em [T] quando precisar que a união seja tratada como uma unidade única.
2. Posição covariante vs. contravariante altera o resultado
Múltiplos candidatos para a mesma variável infer produzem uma união em posições covariantes (ex.: tipos de retorno) e uma interseção em posições contravariantes (ex.: tipos de parâmetros de função). Para funções sobrecarregadas, a inferência usa a última assinatura. Esse comportamento está descrito nas notas de lançamento do TypeScript 2.8.
// Covariante (posição de retorno): união
type Co<T> = T extends { a: () => infer U; b: () => infer U } ? U : never;
type C = Co<{ a: () => string; b: () => number }>; // string | number
// Contravariante (posição de parâmetro): interseção
type Contra<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void } ? U : never;
type D = Contra<{ a: (x: string) => void; b: (x: number) => void }>; // string & number
Conclusão: Reutilizar um mesmo nome infer em múltiplas posições é intencional e poderoso, mas se você obterá uma união ou uma interseção depende da variância das posições. Não assuma que será sempre uma união.
3. Padrões que não conseguem vincular caem no ramo falso
infer só vincula quando o tipo candidato corresponde estruturalmente ao padrão. Se a forma não corresponder, o condicional toma o ramo falso e a variável inferida nunca resolve — portanto, um padrão excessivamente específico retorna silenciosamente seu tipo de fallback em vez do valor esperado.
type FirstArg<T> = T extends (a: infer A, b: infer B) => any ? A : never;
type X = FirstArg<string>; // never — string não corresponde a um tipo de função
Neste exemplo, FirstArg espera um tipo de função. Passar string não corresponde a esse padrão, então o condicional resolve para o ramo falso e retorna never.
Conclusão: Torne os padrões tão flexíveis quanto a correspondência permitir. Use ...args: infer P ou [infer H, ...any[]] em vez de formas com aridade fixa, a menos que você queira especificamente rejeitar outras aridades.
Onde você realmente encontra infer na prática
No código de aplicação, raramente você escreve infer diretamente — você o encontra em definições de tipos de bibliotecas como ComponentProps do React, RequestHandler do Express, PayloadAction do Redux Toolkit e tipos de endpoints do RTK Query, e utiliza os tipos utilitários resultantes. Reconhecer o padrão T extends Wrapper<infer X> ? X : Fallback permite que você leia essas definições em vez de tratá-las como caixas-pretas.
ComponentProps do React. As tipagens do @types/react definem ComponentProps com infer para extrair as props de um componente a partir de seu tipo, ramificando entre elementos host e construtores de componentes. Ao lê-lo, você pode ver a mesma forma T extends ... infer P ... ? P : ... de ReturnType, aplicada aos tipos de elementos do React.
Handlers de requisição do Express. O @types/express do Express modela RequestHandler como um genérico com parâmetros para parâmetros de rota, corpo da resposta, corpo da requisição e query. Bibliotecas que derivam handlers de rota tipados a partir de uma string de caminho combinam esses genéricos com infer em template literals para extrair segmentos :param — o padrão RouteParams mostrado anteriormente é o núcleo desse mecanismo.
PayloadAction do Redux Toolkit. PayloadAction do Redux Toolkit é um tipo útil para extrair com infer, mesmo que sua própria definição dependa de genéricos e tipos condicionais em vez de infer internamente — um lembrete de que infer é como você lê os tipos de uma biblioteca, não necessariamente como a biblioteca é escrita. Você frequentemente escreverá type Payload<A> = A extends PayloadAction<infer P> ? P : never para recuperar o tipo de payload de um reducer a partir de sua action.
import type { PayloadAction } from "@reduxjs/toolkit";
type PayloadOf<A> = A extends PayloadAction<infer P> ? P : never;
type P = PayloadOf<PayloadAction<{ id: string }>>; // { id: string }
Endpoints do RTK Query. O RTK Query (parte do Redux Toolkit) expõe tipos de endpoints gerados onde os tipos de query e resultado são recuperáveis com utilitários baseados em infer. Extrair o tipo de resultado de um endpoint com um condicional que vincula a posição do resultado é uma tarefa rotineira assim que você reconhece o padrão.
Conclusão
infer é um recurso com uma única regra: ele nomeia uma posição dentro de um padrão de tipo condicional e vincula o que quer que corresponda ali, utilizável apenas no ramo verdadeiro. Assim que você enxergar T extends Pattern<infer X> ? X : Fallback como desestruturação no nível de tipos, cada utilitário nativo e tipagem de biblioteca se torna uma variação desse único movimento. Da próxima vez que você se deparar com infer em um arquivo .d.ts, trace o padrão: encontre a posição em que a variável está, verifique se o parâmetro é nu ou encapsulado, e confirme que você está lendo o ramo verdadeiro. Comece substituindo qualquer UnwrapPromise manual na sua base de código por Awaited<T>, e adote infer X extends Constraint onde você atualmente aninha um segundo condicional para restringir um resultado.
Perguntas Frequentes
infer extrai um tipo de um tipo existente por correspondência de padrão dentro de um condicional, enquanto um tipo mapeado transforma cada propriedade de um tipo em uma nova forma. Use infer quando precisar ler um subtipo de uma estrutura, como o tipo de retorno de uma função ou o tipo de elemento de um array. Use um tipo mapeado quando precisar iterar sobre chaves e produzir um novo tipo de objeto. Eles resolvem problemas opostos: infer lê, tipos mapeados reescrevem.
Não. Uma variável de tipo introduzida com infer só está no escopo no ramo verdadeiro do tipo condicional; referenciá-la no ramo falso produz um erro de compilador do TypeScript como 'Cannot find name'. A variável só existe após uma correspondência bem-sucedida, portanto o ramo falso não tem nenhum valor para vincular. Se você precisar de um valor em ambos os resultados, infira no ramo verdadeiro e forneça um tipo de fallback não relacionado no ramo falso.
Por causa da distributividade. Quando o parâmetro de tipo é uma variável de tipo nua e você passa uma união, o TypeScript distribui o condicional sobre cada membro separadamente, de modo que infer resolve uma vez por membro da união e os resultados se acumulam em uma nova união. Isso pode alargar silenciosamente o resultado além do pretendido. Para desativar a distribuição e verificar a união como uma unidade, encapsule o parâmetro em uma tupla, como em [T] extends [Promise<infer U>] ? U : T.
Não, não no TypeScript moderno. Desde o TypeScript 4.5, o Awaited<T> nativo desempacota recursivamente cadeias de Promise e lida com objetos thenables PromiseLike. Um T extends Promise<infer U> ? U : T feito à mão só desempacota um nível e ignora objetos thenable, portanto falha com promises aninhadas. Use Awaited<T> diretamente no código de aplicação, e escreva seu próprio desempacotador baseado em infer apenas como exercício de aprendizado.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.