Typen ableiten, statt dieselben Felder dreimal zu pflegen
Pick, Omit, Partial und Record erzeugen aus einem Datentyp alle Varianten, die Formular, Anlage und Aktualisierung brauchen. Eine Falle gibt es dabei, und sie meldet sich nicht.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Drei Feldlisten für dieselbe Sache, und zwei davon sind veraltet
Ein Datentyp Kunde hat zwölf Felder. Daneben stehen im Projekt: ein Typ für das Anlageformular ohne die vom System vergebene Kennung, ein Typ für die Änderung, bei dem alles optional ist, und ein Typ für die Listenansicht mit vier Feldern. Wenn alle vier von Hand geschrieben sind, kommt beim nächsten neuen Feld eine Änderung an vier Stellen. Nachgetragen wird es an zweien, und der Compiler sagt nichts, weil aus seiner Sicht vier unabhängige Typen existieren, die einfach unterschiedlich aussehen.
Der Fehler zeigt sich später und an unerwarteter Stelle. Das Formular schickt ein Feld mit, das der Änderungstyp nicht kennt, oder die Listenansicht liest ein Feld, das nie geladen wird. Beim Suchen nach der Ursache sieht jeder einzelne Typ richtig aus, denn er ist ja in sich stimmig. Nur die Beziehung zwischen ihnen fehlt, und genau die kann man nicht sehen, wenn sie nirgends aufgeschrieben ist.
Der zweite Fall ist noch unauffälliger: Ein Objekt mit Voreinstellungen steht als Wert im Code, und daneben steht ein handgeschriebener Typ, der dieselben Felder beschreibt. Beide passen am ersten Tag zueinander. Sobald jemand eine Voreinstellung ergänzt, ohne den Typ anzufassen, beschreibt der Typ etwas, das es so nicht mehr gibt. Dabei wäre der Wert die bessere Quelle gewesen, denn er ist das, was tatsächlich läuft.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Der Aufbau im Detail
Der Aufbau
type Formularwerte = Partial<Pick<Kunde, 'name' | 'ort'>> - 01 Der abgeleitete Typ bekommt einen eigenen Namen
type FormularwerteEin Name lohnt sich, sobald der Typ mehr als einmal gebraucht wird. Er taucht in Fehlermeldungen auf und macht sie damit lesbarer, als wenn dort die ausgeschriebene Form steht.
- 02 Macht jedes enthaltene Feld optional
PartialNach dieser Anwendung darf jedes Feld fehlen. Das ist für Formularzwischenstände und für Änderungen richtig und für Antworten aus dem Netz meist falsch, weil dort danach jeder Zugriff eine Prüfung auf undefined verlangt.
- 03 Wählt Felder aus einem bestehenden Typ
PickDie Auswahl wird gegen den Quelltyp geprüft. Ein Feld, das es in Kunde nicht gibt, führt sofort zu einer Meldung. Genau darin liegt der Unterschied zu Omit, das an derselben Stelle stillbleibt.
- 04 Die einzige Stelle, an der die Felder wirklich stehen
KundeAlles andere im Projekt zeigt hierher. Kommt ein Feld dazu, ändert sich diese eine Deklaration, und die abgeleiteten Typen ziehen automatisch mit. Das ist der ganze Zweck der Übung.
- 05 Die Auswahl als Vereinigung von Feldnamen
'name' | 'ort'Hier stehen Feldnamen als Typen, nicht als Zeichenketten im üblichen Sinn. Deshalb schlägt die Entwicklungsumgebung sie vor, und deshalb meldet ein Umbenennen im Quelltyp genau diese Zeile.
Wenn es nicht funktioniert
Das siehst du
Ein Feldname in Omit ist falsch geschrieben, und trotzdem meldet niemand etwas. Das Feld bleibt im abgeleiteten Typ erhalten.
Warum
Omit schränkt seinen zweiten Parameter nicht auf die Schlüssel des Quelltyps ein, sondern nimmt jede Zeichenkette entgegen. Ein Name, den es nicht gibt, entfernt eben nichts.
Was hilft
Nimm für kleine Auswahlen lieber Pick, weil dort geprüft wird. Wo Omit passt, definier einmal einen eigenen Hilfstyp, dessen zweiter Parameter auf keyof des Quelltyps eingeschränkt ist, und benutz im Projekt nur noch diesen.
Das siehst du
Nach einem Omit über einen Typ mit mehreren Varianten lässt sich das Ergebnis nicht mehr über das unterscheidende Feld auseinanderhalten.
Warum
Omit verteilt sich nicht über eine Vereinigung, sondern behandelt sie als ein Ganzes. Aus zwei Varianten wird dabei ein einziger Typ mit den gemeinsamen Feldern, und die Unterscheidung geht verloren.
Was hilft
Bau einen verteilenden Hilfstyp, der die Vereinigung über einen bedingten Typ auseinandernimmt und Omit auf jede Variante einzeln anwendet. Das sind zwei Zeilen, die einmal im Projekt stehen.
Das siehst du
Nach der Umstellung auf Partial steht in der halben Datei eine Meldung, dass ein Wert möglicherweise undefined ist.
Warum
Partial hat jedes Feld optional gemacht, auch die, die fachlich immer gefüllt sind. Der Compiler zieht daraus die richtigen Schlüsse, nur war die Absicht eine andere.
Was hilft
Kombinier statt pauschal zu lockern: Partial über die wirklich optionalen Felder, zusammengeführt mit Pick über die zwingenden. Damit bleibt die Kennung eine Kennung, und nur der Rest darf fehlen.
Das siehst du
Ein neuer Status kommt in den Datentyp, aber die Beschriftungstabelle bleibt unvollständig, ohne dass jemand eine Meldung sieht.
Warum
Die Tabelle ist als Record<string, string> deklariert. Damit ist jeder Schlüssel erlaubt, und Vollständigkeit verlangt niemand.
Was hilft
Setz den Schlüsseltyp auf die Vereinigung der tatsächlichen Werte, also Record<Status, string>. Ab dann meldet der Compiler den fehlenden Eintrag genau an der Stelle, an der die Tabelle steht.
Das siehst du
keyof auf einen Typ liefert unerwartet string und number statt der aufgezählten Feldnamen.
Warum
Der Typ enthält eine Indexsignatur. Damit ist jeder Name erlaubt, und keyof gibt folgerichtig den ganzen erlaubten Bereich zurück statt einer Liste.
Was hilft
Prüf, ob die Indexsignatur wirklich gebraucht wird. Oft ist sie nur da, weil irgendwann ein dynamischer Zugriff einfacher war. Wo sie bleiben muss, arbeitest du mit einer getrennt gepflegten Vereinigung der bekannten Namen.
Sechs Ableitungen, die fast jedes Projekt braucht
- 01 Pick wählt Felder aus und prüft dabei jeden Namen gegen den Quelltyp.
- 02 Omit lässt Felder weg, prüft die Namen aber nicht auf Existenz.
- 03 Partial macht jedes Feld optional, Required dreht das wieder um.
- 04 Record verlangt für jeden Wert einer Vereinigung genau einen Eintrag.
- 05 typeof holt den Typ aus einem Wert, der ohnehin im Code steht.
- 06 keyof liefert die Feldnamen, ohne dass du sie ein zweites Mal aufschreibst.
Was du danach nicht mehr doppelt aufschreibst
Die mitgelieferten Hilfstypen decken die immer gleichen Fälle ab: Felder auswählen, Felder weglassen, alles optional machen, eine Zuordnungstabelle beschreiben. Der Gewinn entsteht nicht durch die einzelne Zeile, sondern dadurch, dass es danach nur noch eine Stelle gibt, an der Felder stehen.
Die Quelle bewusst wählen
Du entscheidest, welcher Typ im Projekt die Wahrheit über die Felder enthält, und leitest alles Weitere daraus ab. Meist ist das der Datentyp, den auch die Schnittstelle liefert, und nicht der Typ, den eine Oberfläche gerade braucht.
Pick und Omit auseinanderhalten
Pick ist die sichere Richtung, weil die Auswahl gegen den Quelltyp geprüft wird. Omit prüft die Schlüssel nicht, ein Tippfehler bleibt dort folgenlos. Du weißt, wann du deshalb lieber die Auswahl aufzählst als das Weglassen.
Partial nur dort, wo wirklich alles optional ist
Für eine Änderung, bei der jedes Feld fehlen darf, ist Partial richtig. Wo die Kennung zwingend bleibt, kombinierst du es, also Partial<Kunde> zusammen mit der ausdrücklich verlangten Kennung, statt später überall auf undefined zu prüfen.
typeof für Typen aus vorhandenen Werten
Steht die Wahrheit als Objekt im Code, holst du dir den Typ mit typeof statt ihn nachzubauen. Zusammen mit as const bleiben dabei auch feste Zeichenketten erhalten, statt zu string zu verallgemeinern.
keyof statt einer zweiten Namensliste
Wo du Feldnamen brauchst, etwa für Spalten oder Formularfelder, nimmst du keyof auf den Quelltyp. Damit fällt ein umbenanntes Feld sofort auf, statt still eine leere Spalte zu erzeugen.
Record für vollständige Zuordnungen
Mit Record über eine Vereinigung fester Werte verlangt der Compiler einen Eintrag für jeden Fall. Ein neuer Status im Datentyp erzeugt damit sofort eine Meldung an der Stelle, an der die Beschriftung fehlt.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Pick und Omit sind nicht spiegelbildlich
Beide erzeugen aus einem Typ einen kleineren, und das ist auch schon die ganze Gemeinsamkeit. Pick verlangt, dass die angegebenen Namen tatsächlich im Quelltyp vorkommen, denn sein zweiter Parameter ist auf keyof des ersten eingeschränkt. Ein Tippfehler wird gemeldet, und die Entwicklungsumgebung schlägt beim Tippen die vorhandenen Felder vor. Omit dagegen nimmt jede Zeichenkette entgegen. Wer versehentlich 'kennnung' statt 'kennung' schreibt, entfernt nichts, bekommt keine Meldung und arbeitet ab diesem Moment mit einem Typ, der ein Feld enthält, das er nicht enthalten sollte.
Daraus folgt eine einfache Faustregel: Wenn die Auswahl kurz ist, nimm Pick. Wenn du aus zwölf Feldern eines entfernen willst, ist Omit natürlich kürzer, und dann lohnt sich ein eigener Hilfstyp im Projekt, dessen zweiter Parameter auf keyof eingeschränkt ist. Diese drei Zeilen schreibst du einmal, und ab dann meldet auch das Weglassen einen falschen Namen.
Die zweite Eigenheit von Omit betrifft Typen mit mehreren Varianten, also Vereinigungen, die über ein Feld unterschieden werden. Omit arbeitet nicht auf jeder Variante einzeln, sondern auf der Vereinigung als Ganzem, und übrig bleibt eine einzige Form mit den gemeinsamen Feldern. Wer danach über das unterscheidende Feld verzweigen will, stellt fest, dass der Compiler die Varianten nicht mehr auseinanderhalten kann. Der Ausweg ist ein verteilender Hilfstyp, der über einen bedingten Typ jede Variante einzeln behandelt.
typeof und as const: wenn der Wert die bessere Quelle ist
Manchmal ist die Wahrheit über eine Struktur kein Typ, sondern ein Objekt, das ohnehin im Code steht: die Voreinstellungen einer Komponente, eine Konfiguration, eine Liste erlaubter Werte. Dann ist es unnötig, den passenden Typ danebenzuschreiben, denn genau dieses Nebeneinander läuft auseinander. Mit type Konfiguration = typeof standardKonfiguration holst du den Typ aus dem Wert. Ändert sich der Wert, ändert sich der Typ mit, und zwar ohne dass irgendwer daran denken muss.
Ohne Zusatz verallgemeinert der Compiler dabei allerdings. Aus einem Feld mit dem Wert 'kompakt' wird string, denn ein Objektfeld ist veränderlich und könnte morgen etwas anderes enthalten. Mit as const hinter dem Objekt bleiben alle Felder unveränderlich und die Werte in ihrer engen Form erhalten. Diese Kombination ist die Grundlage für ein Muster, das häufig gebraucht wird: eine Liste erlaubter Werte als Konstante schreiben, mit as const festhalten und daraus über den Zugriff mit [number] den Vereinigungstyp gewinnen. Damit stehen die erlaubten Werte genau einmal im Projekt, und sie stehen dort, wo sie zur Laufzeit ohnehin gebraucht werden.
Zu diesem Muster gehört keyof als Gegenstück. Wo du Feldnamen brauchst, etwa um Spalten einer Tabelle oder Felder eines Formulars zu benennen, schreibst du keyof Kunde statt einer zweiten Liste mit denselben Namen. Der Unterschied zeigt sich beim Umbenennen: Mit keyof meldet der Compiler jede Stelle, ohne keyof bleibt eine Zeichenkette stehen, die zu nichts mehr passt und still eine leere Spalte erzeugt.
Ableiten aus Funktionen und Schemas
Neben Feldern lassen sich auch Signaturen als Quelle nutzen. ReturnType<typeof erzeugeSpeicher> liefert den Rückgabetyp einer Funktion, ohne dass du ihn benennen musst, Parameters<typeof f> die Parameter als Tupel, und Awaited<T> holt den Typ aus einem Promise heraus. Zusammengesetzt ergibt das Awaited<ReturnType<typeof ladeKunden>>, also den Typ dessen, was am Ende tatsächlich ankommt. Für Funktionen, deren Rückgabewert aus mehreren Schritten zusammengesetzt ist, spart das eine Deklaration, die sonst zwangsläufig hinterherhinkt.
Denselben Gedanken verfolgen Schemabibliotheken. Du beschreibst die Struktur einmal als Schema, bekommst daraus eine Prüfung zur Laufzeit und leitest den TypeScript-Typ aus demselben Schema ab. Damit ist ausgeschlossen, dass Prüfung und Typ auseinanderlaufen, denn beide stammen aus einer Zeile. Wo Daten aus dem Netz oder aus Formularen kommen, ist das die konsequenteste Form der Ableitung, weil sie über die Grenze der Übersetzungszeit hinausreicht.
Bei aller Ableitung gibt es eine Grenze, die man kennen sollte. Ein Typ, der über fünf Ecken aus etwas anderem entsteht, ist beim Lesen kaum noch zu erfassen, und in Fehlermeldungen erscheint er ausgeschrieben in voller Länge. Wenn du beim Hovern nicht mehr erkennst, welche Felder ein Typ hat, ist das der Punkt, an dem eine ausdrückliche Deklaration die bessere Wahl ist. Ableiten spart Pflege, aber es ersetzt nicht die Verständlichkeit.
Der Unterschied zwischen Weglassen und Verbieten
Eine Feinheit, die im Alltag Zeit kostet: Ein optionales Feld und ein Feld, das undefined enthalten darf, sind für TypeScript standardmäßig fast dasselbe. Partial macht aus name: string das Feld name?: string, und damit ist auch die ausdrückliche Übergabe von undefined erlaubt. In vielen Fällen ist das egal. Wo es nicht egal ist, etwa bei einer Änderung, bei der ein fehlendes Feld unverändert lässt und ein ausdrückliches undefined löschen soll, brauchst du die Option exactOptionalPropertyTypes, die genau diesen Unterschied wieder herstellt. Sie gehört nicht zu strict und muss einzeln eingeschaltet werden.
Verwandt ist die Frage, ob ein Zugriff über einen Index oder einen dynamischen Schlüssel undefined liefern kann. Standardmäßig behauptet der Compiler, dass hinter jedem Schlüssel ein Wert steht, obwohl das offensichtlich nicht stimmt. noUncheckedIndexedAccess ändert das und macht aus jedem solchen Zugriff eine Vereinigung mit undefined. In Code, der viel mit Nachschlagetabellen arbeitet, ist das die Option mit dem größten Ehrlichkeitsgewinn und gleichzeitig die mit den meisten neuen Meldungen. Auch sie ist bewusst nicht Teil von strict.
Dazu passende Kurse
Wer im eigenen Projekt Typen aus vorhandenen Typen ableiten will, findet die Muster dafür in den TypeScript-Kursen.
Weil die meisten dieser Ableitungen in Formularen landen, lohnt sich daneben ein Blick auf Kurse zu typisierten Formularen und Komponenten .
Wie sicher bist du beim Thema wirklich?
Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.
Wo du genau das übst
Ein sehr gutes, praxisorientiertes und nachhaltiges Seminar. So sollte es immer sein. Vielen Dank.
Die Schulung ist gerade für Einsteiger oder Entwickler*innen mit eingestaubtem Basiswissen sehr hilfreich.
Ich bin sehr glücklich Franz als Trainer gehabt zu haben. Er hat offensichtlich unglaublich viel Wissen zu den Themen, und schafft es, dieses auch interaktiv und verständlich weiterzugeben.
Häufige Fragen
Wann leite ich ab und wann schreibe ich den Typ aus?
Warum meldet Omit einen falschen Feldnamen nicht?
Was bringt as const im Zusammenspiel mit typeof?
Sind Record und eine Indexsignatur dasselbe?
Deine Ansprechpartner
Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.
Yves Hoppe
Weiterbildung & Beratung
Hilft dir, aus dem Webentwicklung-Programm den passenden Kurs für deinen Stand zu finden.
Norbert Jansen
Beratung & Inhouse
Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.
Eine Feldliste im Projekt statt vier, die auseinanderlaufen
In den TypeScript-Kursen bei cmt leitest du Typen an eigenen Datenmodellen ab und siehst dabei, an welchen Stellen die mitgelieferten Hilfstypen still danebengreifen.