Menu

Exclude, Extract e NonNullable em TypeScript explicados

Exclude, Extract e NonNullable filtram os membros de um union type. Veja o que cada um mantém e remove, como selecionar membros de uma union pelo formato, como eles são montados com conditional types e como diferem de Omit e Pick.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Exclude<T, U> remove membros de um union type, Extract<T, U> mantém só os membros que combinam, e NonNullable<T> remove null e undefined. Os três filtram unions; eles não mexem em propriedades de objetos.

Como todo utility type, eles existem só em tempo de compilação. A última linha é rejeitada pelo compilador; o JavaScript que roda não faz ideia do que é Visible.

Exclude, Extract e NonNullable em resumo

TipoMantémExemploResultado
Exclude<T, U>membros de T não atribuíveis a UExclude<"a" | "b" | "c", "a">"b" | "c"
Extract<T, U>membros de T atribuíveis a UExtract<"a" | "b" | "c", "a" | "z">"a"
NonNullable<T>tudo exceto null e undefinedNonNullable<string | null | undefined>string

"Atribuível a U" é a expressão-chave. U não precisa ser uma lista de membros exatos; pode ser um tipo mais amplo em que vários membros se encaixam:

Selecionando membros de uma union pelo formato

Em uma discriminated union, Extract com um formato parcial tira as variantes que têm esse formato. Esse é o jeito habitual de dar nome a uma variante de uma union que você não escreveu:

O formato { type: "click" } é supertipo só da variante click, então é essa que Extract mantém. Um formato que combina com várias variantes (digamos { x: number }, se duas o tivessem) mantém todas elas.

NonNullable

NonNullable<T> é a versão de tipo de uma verificação !== null && !== undefined. Ele aparece quando você deriva um tipo de algo opcional:

User["manager"] é { name: string } | null | undefined (o ? adiciona undefined), e NonNullable tira os dois. Desde o TypeScript 5.5, o compilador também infere esse predicado sozinho, então raw.filter((s) => s != null) já tem o tipo string[]; o s is NonNullable<typeof s> explícito deixa a intenção visível e funciona em versões mais antigas. A definição é type NonNullable<T> = T & {}: o tipo de objeto vazio {} aceita todo valor exceto null e undefined, então fazer a interseção com ele remove exatamente esses dois.

Como eles são montados

Exclude e Extract são conditional types de uma linha na biblioteca padrão:

type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;

Quando T é uma union, um conditional type é aplicado a cada membro separadamente (ele se distribui), e membros never desaparecem do resultado. Então Exclude<"a" | "b", "a"> vira ("a" extends "a" ? never : "a") | ("b" extends "a" ? never : "b"), que é never | "b", que é "b". A página de conditional types cobre a distribuição em detalhe.

Exclude vs Omit, Extract vs Pick

Os nomes são fáceis de confundir porque os dois pares "removem" ou "mantêm" alguma coisa. A diferença é sobre o que eles operam:

Funciona comRemove ou mantémExemplo
Exclude<T, U>uma unionmembros da unionExclude<"a" | "b", "a"> → "b"
Extract<T, U>uma unionmembros da unionExtract<"a" | "b", "a"> → "a"
Omit<T, K>um tipo de objetopropriedadesOmit<User, "password">
Pick<T, K>um tipo de objetopropriedadesPick<User, "name">

Usar Exclude em um tipo de objeto é um deslize comum, e ele falha em silêncio:

Account não é atribuível a "password", então Exclude o mantém inteiro. O próprio Omit é montado com Exclude aplicado às chaves: Omit<T, K> é Pick<T, Exclude<keyof T, K>>. Veja Omit para mais detalhes.

Um Exclude mais estrito

O segundo parâmetro de Exclude aceita qualquer tipo, então um erro de digitação não remove nada e não gera erro. Adicionar uma restrição faz o compilador verificar se o que você exclui está mesmo na union:

index.ts(7,37): error TS2344: Type '"drafft"' does not satisfy the constraint 'Status'.

Corrija a grafia e compila, com Strict igual a "published" | "archived". O mesmo truque funciona para Extract e para um Omit estrito (K extends keyof T).

Perguntas frequentes

O que Exclude faz no TypeScript?

Exclude<T, U> remove da union T todo membro que seja atribuível a U. Exclude<"a" | "b" | "c", "a"> é "b" | "c". Para remover vários membros, passe uma union como U: Exclude<T, "a" | "b">.

Qual a diferença entre Exclude e Extract?

São opostos. Exclude<T, U> mantém os membros de T que não são atribuíveis a U; Extract<T, U> mantém só os que são. Para o mesmo T e o mesmo U, os dois resultados juntos formam T.

Qual a diferença entre Exclude e Omit?

Exclude trabalha com um union type e remove membros da union. Omit trabalha com um tipo de objeto e remove propriedades. Exclude<User, "password"> não faz nada útil (retorna User), enquanto Omit<User, "password"> dá o tipo de objeto sem essa propriedade. Omit é construído sobre Exclude: ele exclui a chave de keyof T.

O que NonNullable faz no TypeScript?

NonNullable<T> remove null e undefined de um tipo: NonNullable<string | null | undefined> é string. Ele é definido como T & {}, já que {} aceita todo valor exceto null e undefined.

Por que Exclude não dá erro para um membro que não existe?

O segundo parâmetro dele não tem restrição, então qualquer tipo é aceito, e um erro de digitação como Exclude<Status, "drafft"> não remove nada em silêncio. Defina uma versão mais estrita, type StrictExclude<T, U extends T> = Exclude<T, U>, para que o compilador verifique se U faz parte de T.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR