TypeScript avançado: Utility Types e padrões de tipagem que você precisa conhecer
O sistema de tipos do TypeScript vai muito além de anotações básicas. Usar os recursos corretos de tipagem não apenas previne bugs em tempo de compilação, mas também documenta a arquitetura e reduz duplicação de contratos.
Neste artigo, exploramos desde os Utility Types essenciais até padrões de tipagem avançada com tipos condicionais e inferência.
1. O kit essencial de Utility Types
Partial<T> e Required<T>
Ideais para cenários de atualização parcial (patch) ou para garantir que campos opcionais sejam obrigatórios em etapas específicas do fluxo:
interface User {
id: string;
name: string;
email: string;
avatarUrl?: string;
}
// Para mutações parciais (ex: PATCH /users/:id)
type UpdateUserInput = Partial<Omit<User, "id">>;
// Exige que todas as propriedades sejam definidas (ex: perfil completo)
type CompleteUserProfile = Required<User>;
Pick<T, K> e Omit<T, K>
Permitem recortar ou remover propriedades de um contrato existente, garantindo que o tipo derivado continue sincronizado com o modelo original:
type PublicProfile = Pick<User, "id" | "name" | "avatarUrl">;
type CreateUserInput = Omit<User, "id">;
Record<K, V>
Essencial para mapear dicionários, tabelas de tradução ou handlers:
type Role = "admin" | "editor" | "viewer";
interface PermissionConfig {
canWrite: boolean;
canDelete: boolean;
}
const rolePermissions: Record<Role, PermissionConfig> = {
admin: { canWrite: true, canDelete: true },
editor: { canWrite: true, canDelete: false },
viewer: { canWrite: false, canDelete: false },
};
2. Tipagem Avançada: Elevando o nível
Conditional Types e a palavra-chave infer
Tipos condicionais permitem criar lógicas do tipo "se T estende U, então X, senão Y". Com o operador infer, podemos extrair tipos internos de dentro de estruturas genéricas, como funções, promises ou tuplas.
// Exemplo: Extrair o tipo resolvido de uma Promise ou retornar o próprio tipo
type FlattenPromise<T> = T extends Promise<infer U> ? FlattenPromise<U> : T;
type A = FlattenPromise<Promise<string>>; // string
type B = FlattenPromise<Promise<Promise<number>>>; // number (recursivo)
// Exemplo: Extrair o primeiro argumento de qualquer função
type FirstArgument<T> = T extends (first: infer Arg, ...rest: any[]) => any ? Arg : never;
function notifyUser(userId: string, message: string) {}
type UserIdType = FirstArgument<typeof notifyUser>; // string
Template Literal Types
Introduzidos para manipular strings a nível de tipo, são perfeitos para APIs tipadas, canais de eventos e design systems:
type EventType = "user" | "order" | "invoice";
type Action = "created" | "updated" | "deleted";
// Gera automaticamente: "user:created" | "user:updated" | ...
type DomainEvent = `${EventType}:${Action}`;
function emitEvent(event: DomainEvent) {
console.log(`Disparando: ${event}`);
}
emitEvent("user:created"); // ✅ Válido
// emitEvent("user:archived"); // ❌ Erro de compilação!
Dicas práticas para produção
- Evite
anya todo custo: Prefiraunknownquando não souber a estrutura de entrada, combinando com type guards (typeof,instanceofou validação com Zod). - Não crie tipos complexos prematuramente: Comece com interfaces simples e extraia utilitários conforme duplicar código se tornar um problema real de manutenção.
- Use tipos para comunicar intenção de negócio: Aliases bem nomeados tornam o código autoexplicativo para qualquer membro do time.