TypeScript

Boas Práticas de Utility Types em TypeScript: Partial, Required, Pick, Omit e Outros para Times Ágeis

16 min de leitura

Boas Práticas de Utility Types em TypeScript: Partial, Required, Pick, Omit e Outros para Times Ágeis

Introdução aos Utility Types 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. 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 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. Partial, Required e Readonly: Modificando Obrigatoriedade e Mutabilidade Partial: Tornando Propriedades Opcionais transforma todas as propriedades de um tipo em opcionais. Isso é útil quando você precisa de

<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 &quot;função de tipo&quot;. 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&lt;T&gt;</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&lt;Usuario&gt;;

// Agora todas as propriedades são opcionais

const atualizacao: UsuarioAtualizado = {

nome: &quot;João Silva&quot;

// 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&lt;T&gt;</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?: &quot;claro&quot; | &quot;escuro&quot;;

idioma?: string;

notificacoes?: boolean;

fontSize?: number;

}

// Todas as propriedades agora são obrigatórias

type ConfiguracaoCompleta = Required&lt;Configuracao&gt;;

const config: ConfiguracaoCompleta = {

tema: &quot;claro&quot;,

idioma: &quot;pt-BR&quot;,

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&lt;T&gt;</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&lt;Produto&gt;;

const produto: ProdutoImuvel = {

id: 1,

nome: &quot;Notebook&quot;,

preco: 3000

};

// Erro de compilação: Cannot assign to &#039;preco&#039; 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&lt;T, K&gt;</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&lt;Pessoa, &quot;id&quot; | &quot;nome&quot;&gt;;

const resumo: PessoaResumo = {

id: 1,

nome: &quot;Maria&quot;

// email, endereco e telefone não existem aqui

};

// Também funciona com tipos de função

type DadosContato = Pick&lt;Pessoa, &quot;email&quot; | &quot;telefone&quot;&gt;;

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&lt;T, K&gt;</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&lt;Artigo, &quot;criadoEm&quot; | &quot;atualizadoEm&quot;&gt;;

const artigo: ArtculoSemAuditoria = {

id: 1,

titulo: &quot;TypeScript na Prática&quot;,

conteudo: &quot;...&quot;,

autorId: 5

};

// Use quando você quer &quot;quase tudo&quot; de um tipo, exceto alguns campos

type ArtgigoPublico = Omit&lt;Artigo, &quot;autorId&quot; | &quot;criadoEm&quot; | &quot;atualizadoEm&quot;&gt;;</code></pre>

<p>Use <code>Omit</code> quando deseja trabalhar com &quot;a maioria&quot; 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&lt;K, T&gt;</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 = &quot;admin&quot; | &quot;usuario&quot; | &quot;visitante&quot;;

// Garante que temos uma entrada para cada perfil

type PermissoesPerPerfil = Record&lt;Perfil, string[]&gt;;

const permissoes: PermissoesPerPerfil = {

admin: [&quot;ler&quot;, &quot;escrever&quot;, &quot;deletar&quot;, &quot;gerenciar&quot;],

usuario: [&quot;ler&quot;, &quot;escrever&quot;],

visitante: [&quot;ler&quot;]

// 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&lt;StatusCode, string&gt;;

const mensagens: MensagensErro = {

200: &quot;OK&quot;,

404: &quot;Não encontrado&quot;,

500: &quot;Erro interno&quot;

};</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&lt;T, U&gt;</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 = &quot;pendente&quot; | &quot;aprovado&quot; | &quot;rejeitado&quot; | &quot;cancelado&quot;;

// Removemos o status &quot;cancelado&quot;

type StatusAtivo = Exclude&lt;Status, &quot;cancelado&quot;&gt;;

const status: StatusAtivo = &quot;pendente&quot;; // OK

// const status2: StatusAtivo = &quot;cancelado&quot;; // Erro!

// Mais útil com tipos complexos

type Tipos = string | number | boolean | null | undefined; type TiposValidos = Exclude&lt;Tipos, null | undefined&gt;;

const valor: TiposValidos = &quot;texto&quot;; // OK

const nulo: TiposValidos = null; // Erro!</code></pre>

<h3>Extract: Mantendo Apenas Tipos Específicos</h3>

<p><code>Extract&lt;T, U&gt;</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 = &quot;click&quot; | &quot;scroll&quot; | &quot;resize&quot; | &quot;load&quot; | &quot;error&quot;; type EventosDeMouse = Extract&lt;Evento, &quot;click&quot; | &quot;scroll&quot;&gt;;

const evento: EventosDeMouse = &quot;click&quot;; // OK

// const evento2: EventosDeMouse = &quot;load&quot;; // Erro!

// Use para filtrar tipos de uma grande union

type HTTPMethod = &quot;GET&quot; | &quot;POST&quot; | &quot;PUT&quot; | &quot;DELETE&quot; | &quot;PATCH&quot; | &quot;HEAD&quot;; type MethodosInseguros = Extract&lt;HTTPMethod, &quot;POST&quot; | &quot;PUT&quot; | &quot;DELETE&quot; | &quot;PATCH&quot;&gt;;</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&lt;T&gt; = T extends Array&lt;infer U&gt; ? U : T;

type Str = Flatten&lt;string[]&gt;; // string

type Num = Flatten&lt;number&gt;; // number

// Mais prático: Verificar se é uma função e extrair tipo de retorno

type ReturnType&lt;T&gt; = T extends (...args: any[]) =&gt; infer R ? R : never;

function saudar(nome: string): string {

return Olá, ${nome};

}

type ResultadoSaudacao = ReturnType&lt;typeof saudar&gt;; // 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: &quot;admin&quot; | &quot;usuario&quot;;

ativo: boolean;

criadoEm: Date;

}

// DTO para criação (sem id e datas, sem role)

type CriarUsuarioDTO = Omit&lt;Usuario, &quot;id&quot; | &quot;criadoEm&quot; | &quot;role&quot;&gt;;

// Para atualizar, tudo é opcional

type AtualizarUsuarioDTO = Partial&lt;Omit&lt;Usuario, &quot;id&quot; | &quot;criadoEm&quot;&gt;&gt;;

// Resposta pública (sem senha)

type UsuarioPublico = Omit&lt;Usuario, &quot;senha&quot;&gt;;

// Apenas dados de contato

type ContatoUsuario = Pick&lt;Usuario, &quot;email&quot; | &quot;telefone&quot;&gt;;

// Estados de autenticação

type AuthStatus = &quot;autenticado&quot; | &quot;nao-autenticado&quot; | &quot;expirado&quot;;

type AuthStatusValido = Exclude&lt;AuthStatus, &quot;expirado&quot;&gt;;

// Permissões por role

type RolePermissoes = Record&lt;Usuario[&quot;role&quot;], string[]&gt;;

const permissoes: RolePermissoes = {

admin: [&quot;ler&quot;, &quot;escrever&quot;, &quot;deletar&quot;, &quot;gerenciar-usuarios&quot;],

usuario: [&quot;ler&quot;, &quot;escrever&quot;]

};

// Simulando uma API

class UsuarioService {

// Criação: precisa do DTO específico

criar(dados: CriarUsuarioDTO): UsuarioPublico {

console.log(&quot;Criando usuário com:&quot;, 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: &quot;João&quot;, email: &quot;joao@example.com&quot; });

// OK: todos os campos obrigatórios presentes

service.criar({

nome: &quot;João Silva&quot;,

email: &quot;joao@example.com&quot;,

senha: &quot;segura123&quot;,

ativo: true,

role: &quot;usuario&quot;

});

// OK: Partial permite qualquer combinação

service.atualizar(1, { nome: &quot;João Silva Junior&quot; });</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&lt;T&gt;</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&lt;ValorOuNada&gt;; // string

const valor: ValorSeguro = &quot;texto&quot;; // OK

// const nulo: ValorSeguro = null; // Erro!</code></pre>

<h3>Readonly com Arrays</h3>

<p><code>ReadonlyArray&lt;T&gt;</code> ou <code>readonly T[]</code> torna um array imutável.</p>

<pre><code class="language-typescript">type ListaImutavel = readonly string[];

const cores: ListaImutavel = [&quot;vermelho&quot;, &quot;azul&quot;];

// Erro: Property &#039;push&#039; does not exist on type &#039;readonly string[]&#039;

// cores.push(&quot;verde&quot;);</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; // &quot;timeout&quot; | &quot;retries&quot; | &quot;debug&quot;

function obterConfig&lt;K extends keyof Config&gt;(chave: K): Config[K] {

return {} as Config[K];

}

const timeout = obterConfig(&quot;timeout&quot;); // 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&lt;Artigo, &quot;senha&quot;&gt;</code> deixa absolutamente claro que você quer &quot;tudo menos senha&quot;, enquanto <code>Pick&lt;Usuario, &quot;email&quot; | &quot;telefone&quot;&gt;</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 &amp; Generic Constraints</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em TypeScript

Dominando Mixins em TypeScript: Composição de Comportamentos sem Herança em Projetos Reais
Dominando Mixins em TypeScript: Composição de Comportamentos sem Herança em Projetos Reais

O Problema da Herança Clássica Quando começamos a programar orientada a objet...

Guia Completo de Formulários com TypeScript: React Hook Form e Zod Integrados
Guia Completo de Formulários com TypeScript: React Hook Form e Zod Integrados

Introdução: Por que React Hook Form com Zod? Trabalhar com formulários em Rea...

Dominando Classes em TypeScript: Modificadores, Readonly e Parameter Properties em Projetos Reais
Dominando Classes em TypeScript: Modificadores, Readonly e Parameter Properties em Projetos Reais

Introdução aos Modificadores de Acesso em TypeScript Os modificadores de aces...