Menu

TypeScript Partial und Required: Beispiele und Deep Partial

Partial<T> macht jede Eigenschaft von T optional, und genau das ist der Typ eines Update- oder Patch-Objekts. Partial in Update-Funktionen, warum es nur eine Ebene tief wirkt, wie du ein DeepPartial schreibst, die Falle mit explizitem undefined und das Gegenstück Required<T>.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Partial<T> ist ein eingebauter Utility Type, der jede Eigenschaft von T optional macht. Er ist der natürliche Typ für ein Update oder einen Patch: Der Aufrufer schickt nur die Felder, die sich geändert haben.

Partial<User> ist { id?: number; name?: string; email?: string }. Die Felder, die du übergibst, prüft der Compiler trotzdem: { nmae: "x" } oder { name: 42 } ist ein Fehler, und genau das macht Partial besser als einen losen Parameter vom Typ object oder any.

Wie Partial definiert ist

Partial ist ein einzeiliger Mapped Type in der Standardbibliothek von TypeScript:

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

Für jeden Schlüssel P von T deklariert es eine optionale Eigenschaft mit demselben Typ. Weil es über keyof T iteriert, behält es readonly bei Eigenschaften, die es hatten. Als gewöhnlicher Typ hat es keine Wirkung zur Laufzeit: Das als changes übergebene Objekt ist in beiden Fällen dasselbe Objekt.

Lesen aus einem Partial ergibt T | undefined

Jede Eigenschaft eines Partial<T> kann fehlen, also ergibt das Lesen den Eigenschaftstyp plus undefined. Der Compiler zwingt dich, den fehlenden Fall zu behandeln:

{ ...defaults, ...opts } ist der übliche Weg, aus einem Partial<Options> wieder ein vollständiges Options zu machen: Zuerst die Standardwerte spreaden und die übergebenen Werte sie überschreiben lassen.

Die Falle mit explizitem undefined

Eine optionale Eigenschaft kann fehlen, sie kann aber auch mit dem Wert undefined vorhanden sein. Der Objekt-Spread kopiert dieses undefined über den echten Wert, und der Ergebnistyp zeigt das nicht:

Das beißt, wenn ein Patch aus einem Formular oder einem Query-String gebaut wird, in dem leere Felder zu undefined werden. Die Compiler-Option exactOptionalPropertyTypes (nicht Teil von strict) macht { name: undefined } für name?: string zu einem Compilerfehler, solange du nicht name?: string | undefined schreibst, und verhindert das Problem so an der Quelle.

Partial ist flach

Partial macht nur die Eigenschaften der obersten Ebene optional. Ein verschachteltes Objekt muss, wenn du es übergibst, vollständig sein:

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

Die Eigenschaft editor ist optional, hat aber, sobald sie vorhanden ist, unverändert den Typ { fontSize: number; tabSize: number }. Für Einstellungsobjekte, API-Patches und Test-Fixtures willst du oft optionale Eigenschaften auf jeder Ebene. Dafür braucht es einen rekursiven Typ.

DeepPartial: ein rekursives Partial

TypeScript hat keine eingebaute tiefe Version, aber sie ist in ein paar Zeilen geschrieben. Funktionen und Arrays bleiben, wie sie sind, weil optionale Array-Elemente [undefined] erlauben würden:

Der Typ ist rekursiv, das Zusammenführen nicht. applySettings führt editor von Hand zusammen, weil auch der Objekt-Spread flach ist. Eine generische Funktion für tiefes Zusammenführen gibt es in Bibliotheken wie lodash (merge), und ihre Typisierung ist schwieriger als der Typ oben.

Required: das Gegenteil von Partial

Required<T> entfernt das ? bei jeder Eigenschaft. Definiert ist es mit dem Modifikator -?, der außerdem undefined aus dem Typ jeder Eigenschaft entfernt:

Das Muster ist dasselbe wie bei Partial, nur umgekehrt: Nutzer einer API übergeben eine lockere Konfiguration, und der Code im Inneren arbeitet mit einer Required-Version, in der jeder Wert garantiert existiert. Der Spread hat dieselbe Lücke wie die Update-Funktion oben: Ein Aufrufer, der explizit port: undefined übergibt, überschreibt den Standardwert mit undefined, und der Compiler akzeptiert das, solange exactOptionalPropertyTypes nicht aktiv ist. Required ist auf dieselbe Weise flach wie Partial.

Nur einige Eigenschaften optional oder verpflichtend machen

Partial und Required gelten für jede Eigenschaft. Um nur einige zu ändern, teilst du den Typ mit Pick und Omit auf und setzt ihn wieder zusammen:

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
TypWirkungTief?
Partial<T>jede Eigenschaft optionalnein
Required<T>jede Eigenschaft verpflichtend, undefined entferntnein
DeepPartial<T> (selbst geschrieben)optional auf jeder Ebeneja
PartialBy<T, K> (selbst geschrieben)nur die Schlüssel K optionalnein
Readonly<T>jede Eigenschaft readonlynein

Häufig gestellte Fragen

Was macht Partial in TypeScript?

Partial<T> erzeugt einen Typ, in dem alle Eigenschaften von T optional sind. Für interface User { name: string; email: string } ist Partial<User> gleich { name?: string; email?: string }, also sind {}, { name: "Ada" } und ein vollständiger User gültige Werte.

Wirkt Partial in TypeScript in die Tiefe?

Nein, Partial betrifft nur die Eigenschaften der obersten Ebene. Ein verschachteltes Objekt in einem Partial<T> muss trotzdem vollständig sein. Für eine rekursive Version schreibst du einen Typ DeepPartial<T>, der sich selbst auf Eigenschaften mit Objekttyp anwendet.

Was ist das Gegenteil von Partial in TypeScript?

Required<T>. Es entfernt das ? bei jeder Eigenschaft und außerdem undefined aus ihren Typen, also ist Required<{ port?: number }> gleich { port: number }. Definiert ist es als Mapped Type mit dem Modifikator -?.

Wie mache ich nur einige Eigenschaften optional?

Kombiniere Omit, Pick und Partial: type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>. PartialBy<User, "email"> lässt jede Eigenschaft, wie sie war, außer email, das optional wird.

Warum ist eine Eigenschaft nach dem Zusammenführen eines Partial-Updates undefined?

Partial<T> erlaubt, dass eine Eigenschaft mit dem Wert undefined vorhanden ist, und der Objekt-Spread kopiert ihn: { ...user, ...{ name: undefined } } hat name: undefined, obwohl TypeScript das Ergebnis als User typisiert. Filtere undefined-Werte vor dem Zusammenführen heraus oder schalte exactOptionalPropertyTypes ein, damit ein explizites undefined abgelehnt wird.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S