Omit<T, K> é um utility type embutido que cria um tipo de objeto com todas as propriedades de T, exceto as chaves listadas em K. O uso clássico: um tipo User que tem um password, e uma versão pública que não tem.
PublicUser fica sincronizado com User: adicione um campo a User e ele aparece em PublicUser também, enquanto password continua de fora. Omit funciona só com tipos; é a desestruturação em toPublic que remove a propriedade do objeto de verdade.
Omitindo várias chaves
O segundo argumento é uma union, então liste quantas chaves precisar com |:
"O tipo sem os campos gerados pelo servidor" é o Omit mais comum em código real: um único tipo de modelo, com Omit derivando o formato de entrada para formulários de criação e requisições de API.
Como Omit é definido
Omit é montado a partir de dois outros utility types:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Leia de dentro para fora: keyof T é a union de todas as chaves, Exclude<keyof T, K> remove dessa union as indesejadas, e Pick mantém as chaves restantes. Então Omit<User, "password"> é Pick<User, "id" | "name" | "email">. Como Pick mapeia sobre as chaves de T, os modificadores readonly e ? das propriedades restantes são mantidos.
Repare na restrição: K extends keyof any, não K extends keyof T. Esse detalhe é o assunto da próxima seção.
Omit não verifica se a chave existe
keyof any é string | number | symbol, então Omit aceita qualquer chave, inclusive uma que T não tem. Um erro de digitação compila e não remove nada:
Nenhum erro em lugar nenhum, e a senha vai embora no JSON. A restrição frouxa foi escolhida de propósito quando Omit foi adicionado no TypeScript 3.5. Os motivos do time: em código genérico, as chaves omitidas muitas vezes não vêm de T (uma função que mescla dois objetos pode tipar o argumento rest como Omit<T1, keyof T2>), Exclude também não restringe o segundo parâmetro, e uma versão restrita teria quebrado muitos dos tipos Omit escritos à mão que os projetos já tinham. No código do dia a dia, uma versão mais estrita pega o erro de digitação:
index.ts(10,36): error TS2344: Type '"pasword"' does not satisfy the constraint 'keyof User'.
index.ts(11,7): error TS2741: Property 'password' is missing in type '{ id: number; name: string; }' but required in type 'PublicUser'.
O primeiro erro é o que importa: a chave é verificada contra keyof User. O segundo decorre dele, já que nada foi omitido e PublicUser ainda exige password. Corrija a grafia para "password" e compila. Muitos projetos definem esse tipo uma vez em um arquivo de tipos compartilhado e o usam no lugar de Omit.
Sobrescrevendo o tipo de uma propriedade
Uma interface não pode estender outra e mudar uma propriedade para um tipo incompatível: interface ApiUser extends User { id: string } falha com error TS2430: Interface 'ApiUser' incorrectly extends interface 'User'. Omita a propriedade primeiro e depois declare-a de novo:
Com um type alias, a mesma coisa é uma interseção: type ApiUser = Omit<User, "id"> & { id: string }. Sem o Omit, a interseção User & { id: string } daria a id o tipo number & string, que é never.
Omit em union types
Omit não é distributivo. Em uma union, ele parte do keyof da union inteira, que contém só as chaves que todos os membros compartilham, então as propriedades próprias de cada membro desaparecem:
index.ts(8,39): error TS2353: Object literal may only specify known properties, and 'radius' does not exist in type 'NewShape'.
NewShape virou { kind: "circle" | "square" }. Para omitir de cada membro separadamente, aplique Omit dentro de um conditional type distributivo:
T extends unknown ? ... : never é sempre verdadeiro; ele está ali só para fazer o conditional type se distribuir sobre a union. A página de conditional types explica por que isso funciona. Use DistributiveOmit sempre que o tipo de onde você omite for uma discriminated union.
Omitindo uma propriedade aninhada
Omit só olha para as chaves do primeiro nível, então Omit<Settings, "editor.wordWrap"> não remove nada (e, como mostrado acima, não reclama). Para tirar uma propriedade um nível abaixo, omita o pai e adicione-o de volta com o seu próprio Omit:
Settings["editor"] é um indexed access type: ele dá nome ao tipo do objeto aninhado para que Omit possa trabalhar com ele.
Removendo propriedades em tempo de execução
Omit muda só o tipo. Para produzir um objeto sem as propriedades, tire-as com desestruturação, ou escreva um pequeno helper genérico cujo tipo de retorno seja Omit:
O helper restringe K a keyof T, então uma chave com erro de digitação é erro no ponto da chamada. A cópia é rasa: objetos aninhados são compartilhados com o original.
Omit vs Pick vs Exclude
| Funciona com | Mantém | Exemplo | Resultado | |
|---|---|---|---|---|
Omit<T, K> | tipos de objeto | toda propriedade exceto K | Omit<User, "password"> | { id; name; email } |
Pick<T, K> | tipos de objeto | só as propriedades em K | Pick<User, "id" | "name"> | { id; name } |
Exclude<U, M> | union types | membros da union não atribuíveis a M | Exclude<"a" | "b" | "c", "a"> | "b" | "c" |
Escolha entre Omit e Pick pelo que deve acontecer quando o tipo de origem crescer. Omit inclui as propriedades novas automaticamente, o que combina com "tudo exceto o segredo". Pick nunca as inclui, o que combina com "exatamente estes campos" e é mais seguro para qualquer coisa que sai do seu servidor. Pick verifica as chaves contra keyof T; Omit não.
Perguntas frequentes
O que Omit faz no TypeScript?
Omit<T, K> monta um novo tipo de objeto com todas as propriedades de T, exceto aquelas cujas chaves estão em K. Omit<User, "password"> é User sem password. Ele só muda o tipo; não remove nada de um objeto em tempo de execução.
Como omitir várias propriedades no TypeScript?
Passe uma union de chaves como segundo argumento: Omit<User, "password" | "createdAt" | "updatedAt">. Toda chave da union é removida.
Qual a diferença entre Omit e Exclude?
Omit remove propriedades de um tipo de objeto: Omit<{ a: 1; b: 2 }, "a"> é { b: 2 }. Exclude remove membros de um union type: Exclude<"a" | "b", "a"> é "b". Omit é implementado com Exclude: Pick<T, Exclude<keyof T, K>>.
Por que Omit não dá erro para uma chave que não existe?
O parâmetro de chave dele é restrito a keyof any (qualquer string, number ou symbol), não a keyof T, então um erro de digitação como Omit<User, "pasword"> compila e não remove nada. Defina type StrictOmit<T, K extends keyof T> = Omit<T, K> para ter um erro com chaves desconhecidas.
Como remover uma propriedade de um objeto em tempo de execução no TypeScript?
Use desestruturação com um elemento rest: const { password, ...rest } = user;. O TypeScript tipa rest como o objeto sem password, o mesmo formato de Omit<User, "password">, e a propriedade realmente some do novo objeto.