Menu

Partial e Required in TypeScript: esempi e DeepPartial

Partial<T> rende opzionali tutte le proprietà di T, ed è proprio il tipo di un oggetto di aggiornamento o patch. Impara Partial nelle funzioni di aggiornamento, perché è superficiale, come scrivere un DeepPartial, il problema dell'undefined esplicito e il suo opposto, Required<T>.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Partial<T> è un utility type integrato che rende opzionali tutte le proprietà di T. È il tipo naturale per un aggiornamento o una patch: chi chiama invia solo i campi che sono cambiati.

Partial<User> è { id?: number; name?: string; email?: string }. Il compilatore controlla comunque i campi che passi: { nmae: "x" } o { name: 42 } è un errore, ed è questo che rende Partial migliore di un parametro generico object o any.

Come è definito Partial

Partial è un mapped type di una sola riga nella libreria standard di TypeScript:

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

Per ogni chiave P di T, dichiara una proprietà opzionale con lo stesso tipo. Dato che itera su keyof T, mantiene readonly sulle proprietà che lo avevano. Essendo un tipo come un altro, non ha effetti in fase di esecuzione: l'oggetto passato come changes è lo stesso oggetto in ogni caso.

Leggere da un Partial dà T | undefined

Ogni proprietà di un Partial<T> può mancare, quindi leggerne una dà il tipo della proprietà più undefined. Il compilatore ti obbliga a gestire il caso in cui manca:

{ ...defaults, ...opts } è il modo abituale per trasformare un Partial<Options> di nuovo in un Options completo: prima fai lo spread dei valori predefiniti e poi lascia che i valori passati li sovrascrivano.

Il problema dell'undefined esplicito

Una proprietà opzionale può mancare, ma può anche essere presente con il valore undefined. Lo spread degli oggetti copia quell'undefined sopra il valore reale, e il tipo del risultato non lo mostra:

Questo morde quando una patch viene costruita da un modulo o da una query string in cui i campi vuoti diventano undefined. L'opzione del compilatore exactOptionalPropertyTypes (non fa parte di strict) rende { name: undefined } un errore di compilazione per name?: string a meno che tu non scriva name?: string | undefined, e così lo impedisce alla fonte.

Partial è superficiale

Partial rende opzionali solo le proprietà di primo livello. Un oggetto annidato, se lo passi, deve essere completo:

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

La proprietà editor è opzionale, ma quando è presente ha tipo { fontSize: number; tabSize: number }, invariato. Per oggetti di impostazioni, patch di API e fixture di test spesso vuoi proprietà opzionali a ogni livello. Per questo serve un tipo ricorsivo.

DeepPartial: un Partial ricorsivo

TypeScript non ha una versione profonda integrata, ma bastano poche righe. Funzioni e array restano come sono, perché rendere opzionali gli elementi di un array permetterebbe [undefined]:

Il tipo è ricorsivo; l'unione no. applySettings unisce editor a mano perché anche lo spread degli oggetti è superficiale. Una funzione generica di deep merge esiste in librerie come lodash (merge), e la sua tipizzazione è più difficile del tipo qui sopra.

Required: l'opposto di Partial

Required<T> toglie il ? da tutte le proprietà. È definito con il modificatore -?, che toglie anche undefined dal tipo di ogni proprietà:

Lo schema è lo stesso di Partial, al contrario: chi usa un'API passa una configurazione parziale, e il codice interno lavora con una versione Required in cui ogni valore esiste di sicuro. Lo spread ha la stessa falla della funzione di aggiornamento vista sopra: un chiamante che passa port: undefined in modo esplicito sovrascrive il valore predefinito con undefined, e il compilatore lo accetta a meno che exactOptionalPropertyTypes non sia attivo. Required è superficiale proprio come Partial.

Rendere opzionali o obbligatorie solo alcune proprietà

Partial e Required si applicano a tutte le proprietà. Per cambiarne solo alcune, dividi il tipo con Pick e Omit e poi rimettilo insieme:

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
TipoEffettoProfondo?
Partial<T>tutte le proprietà opzionalino
Required<T>tutte le proprietà obbligatorie, undefined rimossono
DeepPartial<T> (scritto da te)opzionale a ogni livellosì
PartialBy<T, K> (scritto da te)solo le chiavi K opzionalino
Readonly<T>tutte le proprietà readonlyno

Domande frequenti

Cosa fa Partial in TypeScript?

Partial<T> crea un tipo con tutte le proprietà di T segnate come opzionali. Per interface User { name: string; email: string }, Partial<User> è { name?: string; email?: string }, quindi {}, { name: "Ada" } e un utente completo sono tutti valori validi.

Partial è profondo in TypeScript?

No, Partial agisce solo sulle proprietà di primo livello. Un oggetto annidato dentro un Partial<T> deve comunque essere completo. Per una versione ricorsiva, scrivi un tipo DeepPartial<T> che si applichi a sé stesso sulle proprietà di tipo oggetto.

Qual è l'opposto di Partial in TypeScript?

Required<T>. Toglie il ? da tutte le proprietà e toglie anche undefined dai loro tipi, quindi Required<{ port?: number }> è { port: number }. È definito come un mapped type con il modificatore -?.

Come rendo opzionali solo alcune proprietà?

Combina Omit, Pick e Partial: type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>. PartialBy<User, "email"> lascia tutte le proprietà come erano, tranne email, che diventa opzionale.

Perché una proprietà resta undefined dopo aver unito un aggiornamento Partial?

Partial<T> permette che una proprietà sia presente con il valore undefined, e lo spread degli oggetti la copia: { ...user, ...{ name: undefined } } ha name: undefined anche se TypeScript tipizza il risultato come User. Filtra i valori undefined prima di unire, oppure attiva exactOptionalPropertyTypes così che un undefined esplicito venga rifiutato.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA