<h2>Introdução aos Utility Types</h2>
<p>Os Utility Types são uma funcionalidade poderosa do TypeScript que permite transformar tipos existentes em novos tipos de forma declarativa e reutilizável. Diferentemente de criar tipos do zero, você trabalha com tipos já existentes e aplica operações sobre eles — como uma "função de tipo". Isso reduz duplicação de código, aumenta a manutenibilidade e deixa seu sistema de tipos mais robusto.</p>
<p>A principal vantagem é que quando você altera um tipo base, todas as transformações realizadas através de Utility Types são atualizadas automaticamente. Imagine ter uma interface <code>User</code> com 20 propriedades; você pode derivar dela um tipo que contém apenas alguns campos, outro que torna tudo opcional, outro que apenas leitura — tudo sem duplicar código. Nesta aula, vamos explorar os Utility Types mais essenciais e suas aplicações práticas.</p>
<h2>Partial, Required e Readonly: Modificando Obrigatoriedade e Mutabilidade</h2>
<h3>Partial: Tornando Propriedades Opcionais</h3>
<p><code>Partial<T></code> transforma todas as propriedades de um tipo em opcionais. Isso é útil quando você precisa de um tipo que permite atualizações parciais ou valores padrão incompletos.</p>
<pre><code class="language-typescript">interface Usuario {
id: number;
nome: string;
email: string;
telefone: string;
}
// Sem Partial, você precisaria recriar a interface inteira com ? em cada propriedade
type UsuarioAtualizado = Partial<Usuario>;
// Agora todas as propriedades são opcionais
const atualizacao: UsuarioAtualizado = {
nome: "João Silva"
// email, telefone e id não são obrigatórios
};
function atualizarUsuario(id: number, dados: UsuarioAtualizado): void {
// Implementação que atualiza apenas os campos fornecidos
console.log(Atualizando usuário ${id} com:, dados);
}</code></pre>
<p>A razão por trás disso é simples: em APIs REST, por exemplo, você raramente recebe todos os campos de uma entidade para atualização. <code>Partial</code> reduz o boilerplate e deixa claro a intenção.</p>
<h3>Required: Tornando Propriedades Obrigatórias</h3>
<p><code>Required<T></code> faz o oposto — transforma propriedades opcionais em obrigatórias. Útil quando você tem um tipo com muitos campos opcionais e precisa garantir que em um contexto específico todos sejam fornecidos.</p>
<pre><code class="language-typescript">interface Configuracao {
tema?: "claro" | "escuro";
idioma?: string;
notificacoes?: boolean;
fontSize?: number;
}
// Todas as propriedades agora são obrigatórias
type ConfiguracaoCompleta = Required<Configuracao>;
const config: ConfiguracaoCompleta = {
tema: "claro",
idioma: "pt-BR",
notificacoes: true,
fontSize: 14
// Remova qualquer uma dessas propriedades e terá erro de compilação
};</code></pre>
<h3>Readonly: Tornando Propriedades Imutáveis</h3>
<p><code>Readonly<T></code> marca todas as propriedades como apenas leitura. Isso previne modificações acidentais e é especialmente valioso em código imutável ou para dados sensíveis.</p>
<pre><code class="language-typescript">interface Produto {
id: number;
nome: string;
preco: number;
}
type ProdutoImuvel = Readonly<Produto>;
const produto: ProdutoImuvel = {
id: 1,
nome: "Notebook",
preco: 3000
};
// Erro de compilação: Cannot assign to 'preco' because it is a read-only property
// produto.preco = 2500;</code></pre>
<h2>Pick, Omit e Record: Selecionando e Criando Tipos Estruturados</h2>
<h3>Pick: Selecionando Propriedades Específicas</h3>
<p><code>Pick<T, K></code> permite selecionar apenas as propriedades que você deseja de um tipo. Diferente de <code>Partial</code>, ele não muda obrigatoriedade — apenas filtra quais campos existem.</p>
<pre><code class="language-typescript">interface Pessoa {
id: number;
nome: string;
email: string;
endereco: string;
telefone: string;
}
// Pegamos apenas id e nome
type PessoaResumo = Pick<Pessoa, "id" | "nome">;
const resumo: PessoaResumo = {
id: 1,
nome: "Maria"
// email, endereco e telefone não existem aqui
};
// Também funciona com tipos de função
type DadosContato = Pick<Pessoa, "email" | "telefone">;
function enviarMensagem(dados: DadosContato): void {
console.log(Enviando para ${dados.email});
}</code></pre>
<p><code>Pick</code> é fundamental quando você quer subconjuntos de tipos para DTOs (Data Transfer Objects), respostas de API ou argumentos de função específicos.</p>
<h3>Omit: Excluindo Propriedades</h3>
<p><code>Omit<T, K></code> é o inverso do <code>Pick</code> — você define quais propriedades remover, não quais manter.</p>
<pre><code class="language-typescript">interface Artigo {
id: number;
titulo: string;
conteudo: string;
autorId: number;
criadoEm: Date;
atualizadoEm: Date;
}
// Removemos os campos de auditoria (data/hora)
type ArtculoSemAuditoria = Omit<Artigo, "criadoEm" | "atualizadoEm">;
const artigo: ArtculoSemAuditoria = {
id: 1,
titulo: "TypeScript na Prática",
conteudo: "...",
autorId: 5
};
// Use quando você quer "quase tudo" de um tipo, exceto alguns campos
type ArtgigoPublico = Omit<Artigo, "autorId" | "criadoEm" | "atualizadoEm">;</code></pre>
<p>Use <code>Omit</code> quando deseja trabalhar com "a maioria" das propriedades. Se precisar de apenas poucos campos, <code>Pick</code> é mais semântico.</p>
<h3>Record: Criando Objetos com Chaves Tipadas</h3>
<p><code>Record<K, T></code> cria um tipo de objeto onde as chaves são de um tipo <code>K</code> e os valores são de tipo <code>T</code>. Útil para mapas tipados e enums.</p>
<pre><code class="language-typescript">type Perfil = "admin" | "usuario" | "visitante";
// Garante que temos uma entrada para cada perfil
type PermissoesPerPerfil = Record<Perfil, string[]>;
const permissoes: PermissoesPerPerfil = {
admin: ["ler", "escrever", "deletar", "gerenciar"],
usuario: ["ler", "escrever"],
visitante: ["ler"]
// Se você remover qualquer uma dessas chaves, terá erro
};
// Também funciona com union types numéricos
type StatusCode = 200 | 404 | 500;
type MensagensErro = Record<StatusCode, string>;
const mensagens: MensagensErro = {
200: "OK",
404: "Não encontrado",
500: "Erro interno"
};</code></pre>
<p><code>Record</code> é particularmente poderoso em situações onde você precisa mapear enums ou unions para valores e quer garantir cobertura completa em tempo de compilação.</p>
<h2>Exclude, Extract e Conditional Types: Manipulando Union Types</h2>
<h3>Exclude: Removendo Tipos de uma Union</h3>
<p><code>Exclude<T, U></code> remove tipos de uma union que correspondem a <code>U</code>. Trabalha especificamente com tipos primitivos e literals, não com interfaces.</p>
<pre><code class="language-typescript">type Status = "pendente" | "aprovado" | "rejeitado" | "cancelado";
// Removemos o status "cancelado"
type StatusAtivo = Exclude<Status, "cancelado">;
const status: StatusAtivo = "pendente"; // OK
// const status2: StatusAtivo = "cancelado"; // Erro!
// Mais útil com tipos complexos
type Tipos = string | number | boolean | null | undefined; type TiposValidos = Exclude<Tipos, null | undefined>;
const valor: TiposValidos = "texto"; // OK
const nulo: TiposValidos = null; // Erro!</code></pre>
<h3>Extract: Mantendo Apenas Tipos Específicos</h3>
<p><code>Extract<T, U></code> faz o oposto — mantém apenas os tipos que correspondem a <code>U</code>, removendo todo o resto.</p>
<pre><code class="language-typescript">type Evento = "click" | "scroll" | "resize" | "load" | "error"; type EventosDeMouse = Extract<Evento, "click" | "scroll">;
const evento: EventosDeMouse = "click"; // OK
// const evento2: EventosDeMouse = "load"; // Erro!
// Use para filtrar tipos de uma grande union
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD"; type MethodosInseguros = Extract<HTTPMethod, "POST" | "PUT" | "DELETE" | "PATCH">;</code></pre>
<h3>Conditional Types: Lógica Dentro do Sistema de Tipos</h3>
<p>Conditional types permitem criar tipos que dependem de outras condições. A sintaxe é <code>T extends U ? X : Y</code>.</p>
<pre><code class="language-typescript">// Exemplo: Se o tipo é array, extraia o elemento; caso contrário, devolva o tipo
type Flatten<T> = T extends Array<infer U> ? U : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number
// Mais prático: Verificar se é uma função e extrair tipo de retorno
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function saudar(nome: string): string {
return Olá, ${nome};
}
type ResultadoSaudacao = ReturnType<typeof saudar>; // string</code></pre>
<p>Conditional types são avançados, mas permitem criar abstrações poderosas e reutilizáveis para seus tipos.</p>
<h2>Exemplo Prático: Integrando Utility Types em Uma Aplicação Real</h2>
<p>Vamos criar um exemplo completo que demonstra como esses Utility Types trabalham juntos em um cenário realista de API com autenticação.</p>
<pre><code class="language-typescript">// Interface base
interface Usuario {
id: number;
nome: string;
email: string;
senha: string;
telefone?: string;
role: "admin" | "usuario";
ativo: boolean;
criadoEm: Date;
}
// DTO para criação (sem id e datas, sem role)
type CriarUsuarioDTO = Omit<Usuario, "id" | "criadoEm" | "role">;
// Para atualizar, tudo é opcional
type AtualizarUsuarioDTO = Partial<Omit<Usuario, "id" | "criadoEm">>;
// Resposta pública (sem senha)
type UsuarioPublico = Omit<Usuario, "senha">;
// Apenas dados de contato
type ContatoUsuario = Pick<Usuario, "email" | "telefone">;
// Estados de autenticação
type AuthStatus = "autenticado" | "nao-autenticado" | "expirado";
type AuthStatusValido = Exclude<AuthStatus, "expirado">;
// Permissões por role
type RolePermissoes = Record<Usuario["role"], string[]>;
const permissoes: RolePermissoes = {
admin: ["ler", "escrever", "deletar", "gerenciar-usuarios"],
usuario: ["ler", "escrever"]
};
// Simulando uma API
class UsuarioService {
// Criação: precisa do DTO específico
criar(dados: CriarUsuarioDTO): UsuarioPublico {
console.log("Criando usuário com:", dados);
return {
id: 1,
...dados,
criadoEm: new Date(),
ativo: true
};
}
// Atualização: Partial permite campos opcionais
atualizar(id: number, dados: AtualizarUsuarioDTO): UsuarioPublico {
console.log(Atualizando usuário ${id} com:, dados);
return {} as UsuarioPublico;
}
// Retorna apenas dados públicos
obter(id: number): UsuarioPublico {
return {} as UsuarioPublico;
}
// Retorna apenas contato
obterContato(id: number): ContatoUsuario {
return {} as ContatoUsuario;
}
}
// Uso
const service = new UsuarioService();
// Erro: senha é obrigatória em CriarUsuarioDTO
// service.criar({ nome: "João", email: "joao@example.com" });
// OK: todos os campos obrigatórios presentes
service.criar({
nome: "João Silva",
email: "joao@example.com",
senha: "segura123",
ativo: true,
role: "usuario"
});
// OK: Partial permite qualquer combinação
service.atualizar(1, { nome: "João Silva Junior" });</code></pre>
<p>Este exemplo mostra como os Utility Types trabalham de mãos dadas: você define uma interface base (<code>Usuario</code>) e cria derivações para casos específicos (DTOs, respostas públicas, subconjuntos). Isso elimina duplicação, mantém sincronização automática e deixa o contrato de dados explícito.</p>
<h2>Outros Utility Types Importantes</h2>
<h3>NonNullable: Removendo null e undefined</h3>
<p><code>NonNullable<T></code> remove <code>null</code> e <code>undefined</code> de uma union.</p>
<pre><code class="language-typescript">type ValorOuNada = string | null | undefined;
type ValorSeguro = NonNullable<ValorOuNada>; // string
const valor: ValorSeguro = "texto"; // OK
// const nulo: ValorSeguro = null; // Erro!</code></pre>
<h3>Readonly com Arrays</h3>
<p><code>ReadonlyArray<T></code> ou <code>readonly T[]</code> torna um array imutável.</p>
<pre><code class="language-typescript">type ListaImutavel = readonly string[];
const cores: ListaImutavel = ["vermelho", "azul"];
// Erro: Property 'push' does not exist on type 'readonly string[]'
// cores.push("verde");</code></pre>
<h3>keyof: Obtendo Chaves de um Tipo</h3>
<p><code>keyof T</code> extrai as chaves de um tipo como uma union. Muito útil com genéricos.</p>
<pre><code class="language-typescript">interface Config {
timeout: number;
retries: number;
debug: boolean;
}
type ConfigKeys = keyof Config; // "timeout" | "retries" | "debug"
function obterConfig<K extends keyof Config>(chave: K): Config[K] {
return {} as Config[K];
}
const timeout = obterConfig("timeout"); // number</code></pre>
<h2>Conclusão</h2>
<p>Os Utility Types são essenciais para escrita de TypeScript profissional e escalável. Três pontos principais a levar: <strong>primeiro</strong>, eles eliminam duplicação de código ao permitir transformações de tipos existentes — ao invés de recriar interfaces manualmente, você compõe tipos através de operações declarativas. <strong>Segundo</strong>, mantêm sincronização automática — quando a interface base muda, todos os tipos derivados são atualizados instantaneamente, evitando inconsistências. <strong>Terceiro</strong>, melhoram a semântica e intenção do código — <code>Omit<Artigo, "senha"></code> deixa absolutamente claro que você quer "tudo menos senha", enquanto <code>Pick<Usuario, "email" | "telefone"></code> mostra exatamente quais campos serão usados.</p>
<p>Domine <code>Partial</code>, <code>Required</code>, <code>Pick</code>, <code>Omit</code> e <code>Record</code> como base, depois explore <code>Exclude</code>, <code>Extract</code>, e conditional types conforme sua aplicação crescer. O investimento em entender esses mecanismos agora vai economizar horas de refatoração e bugs no futuro.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://www.typescriptlang.org/docs/handbook/utility-types.html" target="_blank" rel="noopener noreferrer">TypeScript Handbook: Utility Types</a></li>
<li><a href="https://basarat.gitbook.io/typescript/type-system/generics" target="_blank" rel="noopener noreferrer">TypeScript Deep Dive - Generics</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/2/conditional-types.html" target="_blank" rel="noopener noreferrer">TypeScript Conditional Types Documentation</a></li>
<li><a href="https://www.oreilly.com/library/view/effective-typescript/9781492053736/" target="_blank" rel="noopener noreferrer">Effective TypeScript: 62 Specific Ways to Improve Your TypeScript</a></li>
<li><a href="https://www.pluralsight.com/courses/advanced-typescript" target="_blank" rel="noopener noreferrer">Advanced TypeScript Patterns - Utility Types & Generic Constraints</a></li>
</ul>
<p><!-- FIM --></p>