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
| Tipo | Efeito | Profundo? |
|---|---|---|
Partial<T> | toda propriedade opcional | não |
Required<T> | toda propriedade obrigatória, undefined removido | não |
DeepPartial<T> (o seu) | opcional em todos os níveis | sim |
PartialBy<T, K> (o seu) | só as chaves K opcionais | não |
Readonly<T> | toda propriedade readonly | nã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.