Generics de verdade: variance, constraints e conditional types
- ⬜🎯 Narrowing e discriminated unions: o coração real do TypeScript(TypeScript Profissional)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
Generics são parâmetros de tipo
Assim como uma função recebe valores, um tipo genérico recebe tipos. Array<T> é uma função-de-tipos: dado T, produz o tipo "array de T". Isso elimina duplicação.
// Sem generics: duplica
function firstString(arr: string[]): string | undefined { return arr[0]; }
function firstNumber(arr: number[]): number | undefined { return arr[0]; }
// Com generic: uma função, preserva o tipo
function first<T>(arr: T[]): T | undefined { return arr[0]; }
const name = first(['a', 'b']); // name: string | undefinedConstraints: restringindo T
| Restrição | O que ela permite fazer com o tipo | Quando usar |
|---|---|---|
| Nenhuma | Apenas repassar o valor adiante | Contêiner e função que só transporta |
| Forma mínima exigida | Acessar as propriedades declaradas | A função precisa ler um campo específico |
| Chave de um objeto | Indexar com segurança | Acessador genérico em que a chave precisa existir |
| União de literais | Restringir a um conjunto fechado | Substitui enumeração e mantém a inferência |
| Tipo condicional | Variar o retorno conforme a entrada | Poderoso e caro de ler — só quando a alternativa é sobrecarga repetida |
Quando T é qualquer coisa, você não pode fazer muito com ele. extends diz "T tem que ter pelo menos isso".
function getName<T extends { text: string }>(x: T): string {
return x.name; // ok porque T sempre tem name
}
getName({ text: 'Ana', age: 30 }); // ok, preserva age no tipo
getName({ age: 30 }); // ❌ erro: falta nameUse constraints pra documentar "o que minha função PRECISA do tipo", mas deixe T o mais livre possível — sobre-constringir tira flexibilidade.
Conditional types + infer: metaprogramação sã
Combinação que vira sua arma pra criar tipos a partir de outros.
// ReturnType já vem no lib.es5, mas entenda como é feito:
type MyReturnType<F> = F extends (...args: any[]) => infer R ? R : never;
function fetchUser(): Promise<User> { /* ... */ }
type FetchResult = MyReturnType<typeof fetchUser>; // Promise<User>
// Awaited desempacota Promise recursivamente:
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
type User2 = Awaited<FetchResult>; // UserIsso é fundamental pra libs como tRPC, Zod, Drizzle — elas inferem toda a API a partir dos seus schemas. Dominar infer = entender como libs TS modernas funcionam.
Qual é o objetivo de um tipo genérico bem escrito?
Variance: quando T em diferentes posições se comporta diferente
Considere Array<Cat> vs Array<Animal>. Como Cat é subtipo de Animal, Array<Cat> é subtipo de Array<Animal>. Isso é covariância — mesma direção.
Mas (x: Animal) => void é subtipo de (x: Cat) => void (direção invertida!). Se você pode consumir qualquer Animal, pode consumir Cat. Isso é contravariância.
Por padrão (sem ), TS é bivariante em parâmetros de função — um bug conhecido por compatibilidade. Ligue no tsconfig.
Perguntas frequentes
❓ Quando um genérico é realmente necessário?
❓ Para que servem restrições em genéricos?
❓ Tipo condicional vale a complexidade?
Fixando
Para que serve restringir um parâmetro genérico?
Qual é o sinal de que um tipo genérico foi longe demais?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…