Menu

Partial e Required em TypeScript: exemplos e Deep Partial

Partial<T> torna opcional toda propriedade de T, que é exatamente o tipo de um objeto de atualização ou patch. Veja Partial em funções de update, por que ele é raso, como escrever um DeepPartial, a armadilha do undefined explícito e o oposto dele, Required<T>.

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

Partial<T> é um utility type embutido que torna opcional toda propriedade de T. É o tipo natural para um update ou patch: quem chama envia só os campos que mudaram.

Partial<User> é { id?: number; name?: string; email?: string }. O compilador continua verificando os campos que você passa: { nmae: "x" } ou { name: 42 } é erro, e é isso que torna Partial melhor que um parâmetro frouxo do tipo object ou any.

Como Partial é definido

Partial é um mapped type de uma linha na biblioteca padrão do TypeScript:

type Partial<T> = {
  [P in keyof T]?: T[P];
};

Para cada chave P de T, ele declara uma propriedade opcional com o mesmo tipo. Como ele mapeia sobre keyof T, mantém o readonly nas propriedades que o tinham. Por ser um tipo comum, não tem efeito em tempo de execução: o objeto passado como changes é o mesmo objeto de qualquer jeito.

Ler de um Partial dá T | undefined

Toda propriedade de um Partial<T> pode estar ausente, então ler uma delas dá o tipo da propriedade mais undefined. O compilador obriga você a tratar o caso da ausência:

{ ...defaults, ...opts } é o jeito habitual de transformar um Partial<Options> de volta em um Options completo: espalhe os padrões primeiro e deixe os valores recebidos sobrescrevê-los.

A armadilha do undefined explícito

Uma propriedade opcional pode estar ausente, mas também pode estar presente com o valor undefined. O spread de objeto copia esse undefined por cima do valor real, e o tipo do resultado não mostra isso:

Isso pega você quando um patch é montado a partir de um formulário ou de uma query string em que campos vazios viram undefined. A opção do compilador exactOptionalPropertyTypes (que não faz parte de strict) torna { name: undefined } um erro de compilação para name?: string, a menos que você escreva name?: string | undefined, o que evita o problema na origem.

Partial é raso

Partial só torna opcionais as propriedades do primeiro nível. Um objeto aninhado, se você o passar, precisa estar completo:

index.ts(8,3): error TS2741: Property 'tabSize' is missing in type '{ fontSize: number; }' but required in type '{ fontSize: number; tabSize: number; }'.

A propriedade editor é opcional, mas quando está presente tem o tipo { fontSize: number; tabSize: number }, sem mudança. Para objetos de configuração, patches de API e fixtures de teste, muitas vezes você quer propriedades opcionais em todos os níveis. Isso exige um tipo recursivo.

DeepPartial: um Partial recursivo

O TypeScript não tem uma versão profunda embutida, mas ela cabe em poucas linhas. Funções e arrays ficam como estão, porque tornar opcionais os elementos de um array permitiria [undefined]:

O tipo é recursivo; a mesclagem não é. applySettings mescla editor à mão porque o spread de objeto também é raso. Uma função genérica de mesclagem profunda existe em bibliotecas como o lodash (merge), e tipá-la é mais difícil do que o tipo acima.

Required: o oposto de Partial

Required<T> remove o ? de toda propriedade. Ele é definido com o modificador -?, que também remove undefined do tipo de cada propriedade:

O padrão é o mesmo de Partial, invertido: quem usa uma API passa uma configuração frouxa, e o código interno trabalha com uma versão Required em que se sabe que todo valor existe. O spread tem o mesmo buraco da função de update acima: quem passa port: undefined explicitamente sobrescreve o padrão com undefined, e o compilador aceita, a menos que exactOptionalPropertyTypes esteja ligado. Required é raso do mesmo jeito que Partial.

Tornando só algumas propriedades opcionais ou obrigatórias

Partial e Required se aplicam a todas as propriedades. Para mudar só algumas, divida o tipo com Pick e Omit e junte de novo:

type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;

interface Post {
  id: number;
  title: string;
  body?: string;
}

type NewPost = PartialBy<Post, "id">;      // id optional, title required, body optional
type Published = RequiredBy<Post, "body">; // body now required
TipoEfeitoProfundo?
Partial<T>toda propriedade opcionalnão
Required<T>toda propriedade obrigatória, undefined removidonão
DeepPartial<T> (o seu)opcional em todos os níveissim
PartialBy<T, K> (o seu)só as chaves K opcionaisnão
Readonly<T>toda propriedade readonlynão

Perguntas frequentes

O que Partial faz no TypeScript?

Partial<T> cria um tipo com todas as propriedades de T marcadas como opcionais. Para interface User { name: string; email: string }, Partial<User> é { name?: string; email?: string }, então {}, { name: "Ada" } e um usuário completo são todos valores válidos.

Partial é profundo no TypeScript?

Não, Partial só afeta as propriedades do primeiro nível. Um objeto aninhado dentro de um Partial<T> ainda precisa estar completo. Para uma versão recursiva, escreva um tipo DeepPartial<T> que se aplique a si mesmo nas propriedades do tipo objeto.

Qual é o oposto de Partial no TypeScript?

Required<T>. Ele remove o ? de toda propriedade e também remove undefined dos tipos delas, então Required<{ port?: number }> é { port: number }. Ele é definido como um mapped type com o modificador -?.

Como tornar opcionais só algumas propriedades?

Combine Omit, Pick e Partial: type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>. PartialBy<User, "email"> mantém todas as propriedades como estavam, exceto email, que vira opcional.

Por que uma propriedade fica undefined depois de mesclar um update Partial?

Partial<T> permite que uma propriedade esteja presente com o valor undefined, e o spread de objeto a copia: { ...user, ...{ name: undefined } } tem name: undefined, mesmo que o TypeScript tipe o resultado como User. Filtre os valores undefined antes de mesclar, ou ligue exactOptionalPropertyTypes para que um undefined explícito seja rejeitado.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR