TypeScript: Genéricos avançados do TypeScript
Última atualização: 2026-08-26
Agora que você já domina os conceitos básicos dos genéricos, esta lição passa a abordar tópicos mais avançados — tipos condicionais, infer e tipos mapeados. Essas são ferramentas essenciais para a programação baseada em tipos no TypeScript e constituem a base para a compreensão do código-fonte dos tipos utilitários embutidos.
1. Tipos condicionais
Os tipos condicionais selecionam resultados diferentes com base em condições de tipo — de forma semelhante ao operador ternário no nível do tipo:
(1) Sintaxe básica
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"
type C = IsString<"hello">; // "yes" ("hello" is a string subtype of)
(2) Tipos condicionais e tipos de união (distribuídos)
Quando T é um tipo de união, o tipo condicional avalia cada membro separadamente, de maneira “distribuída”:
type ToString<T> = T extends string ? "string" : "other";
// T = string | number → Evaluate each one separately → "string" | "other"
type Result = ToString<string | number>; // "string" | "other"
(3) Desativar o comportamento distribuído
Coloque o código entre [T] para desativar a avaliação distribuída:
type IsNever<T> = [T] extends [never] ? "yes" : "no";
type A = IsNever<never>; // "yes"
type B = IsNever<string | never>; // "no"(No longer available)
▶ Exemplo: Aplanamento de matriz com segurança de tipos
type Flatten<T> = T extends Array<infer U> ? U : T;
type A = Flatten<string[]>; // string
type B = Flatten<number>; // number
type C = Flatten<boolean[]>; // boolean
Saída:
// Executed successfully
2. A palavra-chave infer
infer “Inferir” uma variável de tipo em uma condição de tipo — essa é uma das ferramentas mais poderosas da programação de tipos no TypeScript:
(1) Inferindo o tipo dos elementos de uma matriz
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type A = ArrayElement<string[]>; // string
type B = ArrayElement<number[]>; // number
type C = ArrayElement<string>; // never(string Not an array)
(2) Inferindo o tipo de retorno de uma função
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type A = GetReturnType<() => string>; // string
type B = GetReturnType<(x: number) => boolean>; // boolean
type C = GetReturnType<(x: string) => void>; // void
type D = GetReturnType<string>; // never(string Not a function)
(3) Inferência dos tipos dos parâmetros de função
type GetParameters<T> = T extends (...args: infer P) => any ? P : never;
type A = GetParameters<(a: string, b: number) => void>; // [string, number]
type B = GetParameters<() => void>; // []
type C = GetParameters<(x: boolean) => string>; // [boolean]
(4) Inferindo o tipo do valor resolvido de uma promessa
type Awaited<T> = T extends Promise<infer U> ? U : T;
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<number[]>>; // number[]
type C = Awaited<string>; // string(No Promise,Return directly)
// nested Promise——Recursive Unpacking
type DeepAwaited<T> = T extends Promise<infer U> ? DeepAwaited<U> : T;
type D = DeepAwaited<Promise<Promise<string>>>; // string
3. Tipos mapeados
Tipos de mapeamento: Como criar um novo tipo com base em um antigo — aplicando transformações a cada propriedade:
(1) Sintaxe básica
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type Optional<T> = {
[K in keyof T]?: T[K];
};
interface User {
name: string;
age: number;
email: string;
}
type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number; readonly email: string }
type OptionalUser = Optional<User>;
// { name?: string; age?: number; email?: string }
(2) Modificadores de mapeamento
Use +/- para adicionar ou remover modificadores:
// Remove readonly
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
// Remove (optional)(?)
type Required2<T> = {
[K in keyof T]-?: T[K];
};
interface Config {
readonly host: string;
readonly port: number;
debug?: boolean;
}
type MutableConfig = Mutable<Config>;
// { host: string; port: number; debug?: boolean } —— readonly Removed
type RequiredConfig = Required2<Config>;
// { readonly host: string; readonly port: number; debug: boolean } —— ? Removed
(3) Remapeamento de teclas (TypeScript 4.1+)
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }
(4) Propriedades do filtro
type OnlyStrings<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
interface Mixed {
name: string;
age: number;
email: string;
active: boolean;
}
type StringProps = OnlyStrings<Mixed>;
// { name: string; email: string } —— Keep only string Type Properties
▶ Exemplo: Como criar um cliente de API com segurança de tipos
// Definition API Routing Type
type ApiRoutes = {
"/users": { response: { id: number; name: string }[] };
"/users/:id": { response: { id: number; name: string }; params: { id: number } };
"/posts": { response: { id: number; title: string }[] };
};
// Extract Response Type Based on Route
type ApiResponse<R extends keyof ApiRoutes> = ApiRoutes[R]["response"];
type UsersResponse = ApiResponse<"/users">;
// { id: number; name: string }[]
type UserResponse = ApiResponse<"/users/:id">;
// { id: number; name: string }
// Type-safe fetch Function
function fetchApi<R extends keyof ApiRoutes>(
route: R,
...args: "params" extends keyof ApiRoutes[R]
? [params: ApiRoutes[R]["params"]]
: []
): Promise<ApiResponse<R>> {
// Implementation Omitted
return {} as any;
}
// Automatically infer parameters and return types at runtime
// let users = fetchApi("/users"); // No parameters required
// let user = fetchApi("/users/:id", { id: 1 }); // Must be provided params
Saída:
// Executed successfully
4. Valores padrão para genéricos
Os parâmetros de tipo genérico podem ter valores padrão — o tipo padrão é usado quando o tipo não pode ser inferido ou não precisa ser especificado:
interface PaginatedResult<T, PageSize = 10> {
data: T[];
total: number;
pageSize: PageSize;
}
// Use the default values
type UserResult = PaginatedResult<User>;
// data: User[]; total: number; pageSize: 10
// Override the default value
type CustomResult = PaginatedResult<User, 20>;
// data: User[]; total: number; pageSize: 20
(1) Restrições aos valores padrão
Os parâmetros de tipo com valores padrão devem vir após os parâmetros sem valores padrão:
// ✅ Correct
type A<T, U = string> = { first: T; second: U };
// ❌ Error——Those with default values U When there is no default value, T Previous
// type B<U = string, T> = { first: T; second: U };
5. Covariância e invariância
Esse é um dos conceitos avançados mais confusos do sistema de tipos do TypeScript — é preciso entendê-lo para compreender por que algumas atribuições são válidas e outras não.
(1) Covariável
"Se A é um subtipo de B, então Container<A> também é um subtipo de Container<B>" — Mudanças na mesma direção:
// string is a subtype of string | number
// string[] Me too (string | number)[] subtypes of → Covariation
let strings: string[] = ["a", "b"];
let mixed: (string | number)[] = strings; // ✅ Covariant Safety
(2) Adjunto (contravariável)
Os tipos de parâmetros de função são inversos — “Se A é um subtipo de B, então (B => void) é um subtipo de (A => void)” — a relação funciona na direção oposta:
// string is a subtype of string | number
// (string | number => void) is a subtype of (string => void) → Inversion
type StringHandler = (arg: string) => void;
type MixedHandler = (arg: string | number) => void;
let mixedHandler: MixedHandler = (arg) => console.log(arg);
let stringHandler: StringHandler = mixedHandler; // ✅ Inversion——Functions that handle broader types can be assigned to variables that handle narrower types.
(3) Por que os argumentos da função estão invertidos?
// If the parameter is covariant——Unsafe
let dogHandler: (dog: Dog) => void = (dog) => dog.bark();
let animalHandler: (animal: Animal) => void = dogHandler; // ❌ Danger!
// Call animalHandler(cat) → dog.bark() called on cat → Runtime Error
// Inverting is the safe option——Functions that handle broader types can safely handle narrower types.
strictFunctionTypes.
▶ Exemplo: Construindo um cliente de API type-safe
Saída:
42
// Definition API Routing Type
type ApiRoutes = {
"/users": { response: { id: number; name: string }[] };
"/users/:id": { response: { id: number; name: string }; params: { id: number } };
"/posts": { response: { id: number; title: string }[] };
};
// Extract Response Type Based on Route
type ApiResponse<R extends keyof ApiRoutes> = ApiRoutes[R]["response"];
type UsersResponse = ApiResponse<"/users">;
// { id: number; name: string }[]
type UserResponse = ApiResponse<"/users/:id">;
// { id: number; name: string }
// Type-safe fetch Function
function fetchApi<R extends keyof ApiRoutes>(
route: R,
...args: "params" extends keyof ApiRoutes[R]
? [params: ApiRoutes[R]["params"]]
: []
): Promise<ApiResponse<R>> {
// Implementation Omitted
return {} as any;
}
// Automatically infer parameters and return types at runtime
// let users = fetchApi("/users"); // No parameters required
// let user = fetchApi("/users/:id", { id: 1 }); // Must be provided params
Saída:
Type-safe API client — no runtime output (compile-time type checking only)
❓ Perguntas Frequentes
P: Qual é a diferença entre
infere o genéricoT? R: O genéricoTé um parâmetro de tipo fornecido pelo chamador, enquantoinferé uma variável de tipo inferida automaticamente dentro de um tipo condicional.Tsignifica “você me diz”, einfersignifica “eu mesmo infiro”.infersó pode ser usado na cláusulaextendsde um tipo condicional.
P: Qual é a relação entre tipos de mapeamento e tipos utilitários (como Partial e Required)? R: Os tipos utilitários são implementados por meio de tipos de mapeamento. Partial, Required, Readonly, Pick, Omit e outros são todos tipos de mapeamento embutidos no TypeScript. Compreender como os tipos de mapeamento funcionam ajudará você a entender como esses tipos utilitários operam, e você também poderá criar seus próprios tipos utilitários personalizados.
P: A covariância e a contravariância são importantes no desenvolvimento do dia a dia? R: Na maioria das vezes, você não precisa se preocupar com elas — a verificação de tipos do TypeScript lida com elas automaticamente. Você só precisa entendê-las ao escrever funções genéricas, funções de ordem superior ou ferramentas de tipos. Iniciantes devem apenas se familiarizar com os conceitos e se aprofundar neles somente quando encontrarem incompatibilidades de tipos em atribuições de funções.
P: Quando um tipo condicional é “distribuído”? R: Quando o T em um tipo condicional é um parâmetro de tipo simples (não envolto em uma tupla, objeto etc.) e é um tipo de união, a avaliação é distribuída. Envolvê-lo em
[T] extends [U]desativa a distribuição. O exemplo IsNever é o cenário mais comum em que a distribuição precisa ser desativada —neveré ignorado durante a distribuição quando é um membro da união.
📖 Resumo
- O tipo condicional
T extends U ? X : Yé um operador ternário no nível de tipo - inferir: Inferir variáveis de tipo em tipos condicionais — inferir elementos de matriz, valores de retorno de função e valores de resolução de Promise
- Um tipo de mapeamento que cria um novo tipo com base em um tipo antigo —
{ [K in keyof T]: T[K] }— suporta modificadores +/- e remapeamento de teclas - Valores padrão genéricos
<T = DefaultValue>Fornecer tipos alternativos para parâmetros genéricos - A covariância (mesma direção) e a contravariância (direção oposta) descrevem as relações entre subtipos para tipos genéricos — os tipos de retorno das funções são covariantes, enquanto os parâmetros são contravariantes
📝 Exercícios
- Problema básico (Dificuldade ⭐): Implemente
IsArray<T>usando tipos condicionais — se T for um tipo de matriz, retorne true; caso contrário, retorne false. TesteIsArray<string[]>eIsArray<number>. - Problema avançado (Dificuldade ⭐⭐): Implemente
Stringify<T>usando um tipo de mapeamento — converta os tipos de todas as propriedades de um objeto parastring. Por exemplo,{ age: number }→{ age: string }. - Problema de desafio (Dificuldade ⭐⭐⭐): Use
infere tipos condicionais para implementarDeepPromise<T>— desembrulhe recursivamente Promises aninhadas até obter um tipo que não seja Promise. O caso de testeDeepPromise<Promise<Promise<Promise<number>>>>deve retornar um número.