TypeScript: Genéricos avançados do TypeScript
Última atualização: 2026-10-22
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
infer e o genérico T?T é um parâmetro de tipo fornecido pelo chamador, enquanto infer é uma variável de tipo inferida automaticamente dentro de um tipo condicional. T significa “você me diz”, e infer significa “eu mesmo infiro”. infer só pode ser usado na cláusula extends de um tipo condicional.[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.