O TypeScript vem com um conjunto de tipos genéricos chamados utility types, que transformam tipos existentes em novos tipos. Eles fazem parte da biblioteca padrão, então você não precisa instalar nada a mais. Aprendê-los substitui uma grande quantidade de definições de tipo repetitivas e manuais.
Introdução aos Utility Types
Os utility types são tipos genéricos predefinidos que realizam transformações de tipo comuns. Eles permitem derivar um novo tipo a partir de um existente, em vez de escrevê-lo do zero. Quando você começa a usá-los, para de copiar e colar definições de interface com pequenas variações.
Partial<>
Partial<> recebe um tipo e torna todas as suas propriedades opcionais. O caso de uso clássico são funções de atualização que aceitam um subconjunto de campos.
interface User {
id: number;
name: string;
email: string;
}
function updateUser(id: number, user: Partial<User>) {
// Implementation to update user
}
updateUser agora aceita qualquer combinação de propriedades de User sem exigir todas elas.
Required<>
Required<> é o inverso de Partial<>. Ele recebe um tipo em que algumas ou todas as propriedades são opcionais e marca todas elas como obrigatórias.
interface User {
id?: number;
name?: string;
email?: string;
}
type CompleteUser = Required<User>;
CompleteUser exige os três campos, independentemente da opcionalidade na interface de origem.
Readonly<>
Readonly<> torna todas as propriedades de um tipo não graváveis após a inicialização. Útil para objetos de configuração e outros dados que não devem ser mutados.
interface User {
id: number;
name: string;
}
const user: Readonly<User> = {
id: 1,
name: 'John Doe',
};
// This will cause an error:
// user.name = 'Jane Doe';
Pick<>
Pick<> constrói um novo tipo a partir de um subconjunto das propriedades de um tipo existente. Prático quando você precisa apenas de parte de uma interface maior.
interface User {
id: number;
name: string;
email: string;
isAdmin: boolean;
}
type Admin = Pick<User, 'id' | 'isAdmin'>;
const adminUser: Admin = {
id: 1,
isAdmin: true,
};
Omit<>
Omit<> é o complemento de Pick<>. Ele produz um tipo que exclui propriedades específicas. Comum para remover campos sensíveis antes de enviar dados aos clientes.
interface User {
id: number;
name: string;
email: string;
password: string;
}
type PublicUser = Omit<User, 'password'>;
const publicUser: PublicUser = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
};
Record<>
Record<> cria um tipo de objeto com um conjunto fixo de chaves e um tipo de valor uniforme. É a escolha certa para tabelas de consulta tipadas.
type Roles = 'admin' | 'user' | 'guest';
interface Permissions {
canEdit: boolean;
canDelete: boolean;
}
type RolePermissions = Record<Roles, Permissions>;
const permissions: RolePermissions = {
admin: { canEdit: true, canDelete: true },
user: { canEdit: true, canDelete: false },
guest: { canEdit: false, canDelete: false },
};
Exclude<>
Exclude<> filtra um tipo de união removendo os membros que correspondem a um tipo informado.
type Status = 'success' | 'error' | 'pending';
type NonErrorStatus = Exclude<Status, 'error'>;
// NonErrorStatus is 'success' | 'pending'
Extract<>
Extract<> é o oposto: ele mantém apenas os membros da união que correspondem a um tipo informado.
type Status = 'success' | 'error' | 'pending';
type ErrorStatus = Extract<Status, 'error' | 'pending'>;
// ErrorStatus is 'error' | 'pending'
NonNullable<>
NonNullable<> remove null e undefined de um tipo. Útil na fronteira entre dados anuláveis e código que espera um valor concreto.
type Name = string | null | undefined;
type ValidName = NonNullable<Name>;
// ValidName is string
ReturnType<>
ReturnType<> infere o tipo de retorno de uma função. Isso faz com que a função seja a única fonte de verdade para o formato de sua saída.
function getUser() {
return { id: 1, name: 'John Doe' };
}
type User = ReturnType<typeof getUser>;
// User is { id: number; name: string; }
InstanceType<>
InstanceType<> extrai o tipo da instância a partir de uma função construtora. Útil quando você passa construtores de classe adiante e precisa referenciar o tipo daquilo que eles produzem.
class User {
constructor(public id: number, public name: string) {}
}
type UserInstance = InstanceType<typeof User>;
// UserInstance is User
Conclusão
Os utility types são uma das partes mais práticas do sistema de tipos do TypeScript. Apenas Partial<> e Omit<> já cobrem a maioria dos casos que você encontrará ao modelar entradas de API e formatos de dados públicos. Vale conhecer os demais para reconhecer quando se aplicam, em vez de escrever definições de tipo redundantes na mão.
O erro mais comum é escrever uma nova interface quando um utility type aplicado a uma já existente resolveria o problema com menos código e melhor acoplamento ao tipo de origem.