Tipos utilitários (Partial, Pick, Omit...) e quando NÃO usar
- ⬜⚙️ Generics de verdade: variance, constraints e conditional types(TypeScript Profissional)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
O arsenal — o que vem no lib padrão
| Tipo | O que faz | Quando usar |
|---|---|---|
| Partial<T> | Todos os campos opcionais | PATCH, merge, form draft (cuidado: veja armadilha) |
| Required<T> | Todos os campos obrigatórios | Garantir que nada é opcional após setup |
| Readonly<T> | Todos os campos readonly | Imutabilidade superficial |
| Pick<T, K> | Seleciona só K | Subconjunto explícito (poucos campos) |
| Omit<T, K> | Remove K | Tudo exceto alguns (muitos campos, poucas exceções) |
| Record<K, V> | Dict K → V | Mapa tipado, config, lookup |
| Exclude<T, U> | T menos o que for atribuível a U | Remover variantes de union |
| Extract<T, U> | T interseccionado com U | Pegar só variantes que casam |
| NonNullable<T> | T sem null/undefined | Garantir valor presente |
| ReturnType<F> | Tipo de retorno de função F | Inferir retorno sem chamar |
| Awaited<T> | Desempacota Promise<T> | Extrair tipo resolvido |
A armadilha: Partial escondendo modelagem fraca
É tentador usar Partial<User> pra representar "estou criando um User aos poucos". Mas isso descarta invariantes: um "rascunho" na verdade tem regras próprias.
// Antipattern
type UserDraft = Partial<User>;
// Problema: { email: undefined, id: 'abc' } compila — invariante perdido
// Melhor: modelar o rascunho explicitamente
type UserDraft = {
email: string; // obrigatório no draft
name?: string; // opcional no draft
age?: number;
// id NÃO existe até save
};
type User = UserDraft & { id: string; createdAt: string };Use sem medo em patches (input de PATCH API, reduce/merge helpers). Evite pra estados de domínio — modele o estado com seu próprio tipo.
Record vs Map: estrutural vs runtime
// Record: tipo estrutural, valor é objeto
const theme: Record<'light' | 'dark', string> = {
light: '#fff',
dark: '#000',
};
// Map: estrutura em runtime (preserva ordem, chaves não-string)
const metrics = new Map<UserId, number>();
metrics.set('abc' as UserId, 42); Record é melhor pra configs com chaves conhecidas (poucas, fixas). Map é melhor pra dados dinâmicos (chaves desconhecidas, muitas inserções/remoções, precisa de size, iteração ordenada).
Você modela o rascunho de um cadastro como `Partial<User>`. Que invariante isso destrói?
Quando NÃO usar utilitários
A regra: se seu tipo nomeado é "usado uma vez e tem significado", nomeie-o. Combinações monstruosas como Omit<Pick<User, 'name' | 'email'>, 'email'> viram ilegíveis. Crie type PublicProfile = { text: string } e fim.
Perguntas frequentes
❓ Quando NÃO usar tipos utilitários?
❓ Derivar tipo do banco ou declarar separado?
❓ Qual utilitário é mais mal usado?
Fixando
`Record<K, V>` e `Map<K, V>` resolvem coisas diferentes. Qual é a distinção que decide a escolha?
Quando um tipo utilitário deixa de ajudar e passa a atrapalhar?
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…