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.