Start / Seminare / Modern TypeScript 7
Modul
Generics und wiederverwendbare APIs
9 Kapitel in diesem Modul-Video · Laufzeit
Transkript
Der gesprochene Text dieses Moduls zum Mitlesen, Überfliegen und Durchsuchen. Ein Klick auf einen Zeitstempel springt an die Stelle im Video.
Generics und wiederverwendbare APIs
0:00 Willkommen zum zweiten Tag. Gestern haben wir einzelne Werte und Zustände beschrieben — heute bauen wir Bausteine, die für viele Typen zugleich funktionieren. Der Leitsatz dafür steht gleich auf der nächsten Folie und ist die kürzeste Erklärung von Generics, die ich kenne: Der Typ ist nicht unbekannt, er ist nur noch nicht festgelegt.
0:19 Nebenbei lösen wir das Problem ein, das Modul 2 offengelassen hat — drei Kennungen der Firma Kurbelwerk, die alle Zeichenketten sind und deshalb munter vertauscht werden konnten. Am Ende des Moduls sind sie es nicht mehr.
Generics und wiederverwendbare APIs
0:32 Der zweite Tag beginnt mit dem anspruchsvollsten Teil des Typsystems, und ich sage das ohne Drohgebärde: Vieles davon werden Sie seltener schreiben als lesen. Wir gehen von generischen Funktionen über die Operatoren, die Typinformation aus vorhandenem Code holen, bis zu Fallunterscheidungen und Umformungen auf Typebene.
0:49 Zum Schluss zwei sehr praktische Themen: Marken, die strukturell gleiche Typen trennen, und die Frage, wo die vernünftige Grenze komplexer Typkonstruktionen liegt.
Generische Funktionen und Typen
0:59 Beginnen wir mit dem Grundgedanken. Ein Typparameter hält etwas offen, ohne es preiszugeben — und dieser feine Unterschied zum abschaltenden Typ ist der ganze Punkt. Er wird trotzdem regelmäßig eingeebnet, weil beide im Editor kurz gleich bequem aussehen. Ein Bild dafür: Denken Sie an einen Paketdienst. Er transportiert Ihr Paket, ohne hineinzuschauen — aber er weiß genau, wem es gehört, und liefert es an denselben Empfänger zurück.
1:25 Das ist ein Typparameter. Der Unterschied zum abschaltenden Typ ist entscheidend: Dort ist der Typ unbekannt und bleibt es — das Paket verschwindet im Sortierzentrum und kommt als anonyme Kiste zurück. Beim Typparameter ist der Typ nur noch nicht festgelegt, und der Aufrufer bekommt genau das zurück, was er hineingegeben hat. Offen halten, ohne zu vergessen.
1:48 Zwei Funktionen, die dasselbe tun: das erste Element einer Liste zurückgeben. Die obere gibt den Typ preis — was herauskommt, ist beliebig, und Sie können damit alles machen, auch Unsinn. Die untere reicht den Typ durch: Geben Sie Aufträge hinein, kommt ein Auftrag heraus. Und sie ist noch in einem zweiten Punkt ehrlicher — sie gibt zu, dass die Liste leer sein könnte. Die Fußzeile bringt es auf den Punkt: Beide übersetzen.
2:14 Nur bei der zweiten meldet sich der Compiler, wenn der Aufrufer das Ergebnis falsch benutzt. Gleicher Aufwand, völlig andere Wirkung. Der erste Punkt ist eine gute Faustregel zum Merken: Ein Typparameter, der nur an einer einzigen Stelle vorkommt, leistet nichts — er ist der abschaltende Typ mit besserer Presse. Ein Typparameter verbindet mindestens zwei Stellen, Eingang und Ausgang, sonst hat er keine Aufgabe. Der zweite: Generics als Selbstzweck, wo ein konkreter Typ genügt hätte.
2:43 Und der dritte betrifft die Lesbarkeit: Einbuchstabige Namen sind Tradition, aber sobald drei davon zusammenkommen, weiß niemand mehr, wofür sie stehen. Nennen Sie sie wie andere Bezeichner auch.
Constraints und Default-Parameter
2:56 Ein völlig offener Typparameter ist beliebig — und damit im Rumpf nutzlos, denn Sie dürfen mit ihm nichts anfangen. Erst eine Einschränkung macht ihn brauchbar. Sehen wir uns an, wie man sie richtig zuschneidet, und was Vorgabewerte dazu beitragen. Ein Constraint legt fest, was ein Typparameter mindestens können muss — es ist die Mindestanforderung an das, was hineingegeben wird.
3:19 Erst dadurch lässt sich im Rumpf überhaupt etwas mit dem Wert anfangen, denn erst dann weiß der Compiler, dass ein bestimmtes Feld existiert. Ein Default-Typparameter ist etwas ganz anderes: Er macht den häufigen Fall bequem, ohne den seltenen zu verbauen. Wichtig ist die Abgrenzung im letzten Satz — er ersetzt kein Constraint, er ergänzt es. Das eine sagt „mindestens so", das andere „wenn du nichts sagst, dann so".
3:44 Oben die Mindestanforderung: Was hier hineingegeben wird, muss eine Nummer tragen. Die Fußzeile erklärt, warum das nötig ist — ohne diese Angabe wäre der Zugriff auf die Nummer ein Fehler, denn der Compiler wüsste nicht, dass es sie gibt. Beachten Sie: Wir fordern genau das eine Feld, nicht einen ganzen Auftragstyp. Damit funktioniert die Funktion auch für Kunden, Räder und Ersatzteile.
4:08 Unten der Vorgabewert: Wer nichts angibt, bekommt eine Seite mit Aufträgen — der häufige Fall wird bequem, der seltene bleibt möglich. Zwei kleine Handgriffe mit großer Wirkung auf die Benutzbarkeit. Die Kunst liegt im Zuschnitt, und die mittleren beiden Punkte beschreiben die zwei Richtungen, in die man danebengreifen kann.
4:27 Zu eng geschnitten schließt gültige Aufrufer aus — Sie fordern einen ganzen Auftrag, obwohl Sie nur die Nummer lesen. Zu weit geschnitten zwingt Sie zu Prüfungen im Rumpf, weil der Compiler zu wenig weiß. Genau richtig ist es, wenn es exakt das nennt, was der Rumpf tatsächlich braucht. Und die schönste Probe steht zum Schluss: Ein gutes Constraint liest sich als fachliche Anforderung — „alles, was eine Nummer hat" — und nicht als technische Hürde.
keyof, typeof und Indexed Access
4:52 Jetzt kommt eine Gruppe von Operatoren, die im Alltag sehr viel bringt und erstaunlich wenig bekannt ist. Ihr gemeinsames Prinzip: Typinformation holen, statt sie ein zweites Mal zu schreiben. Das ist der wirksamste Schutz gegen auseinanderlaufende Definitionen — und er kostet keine Zeile Pflege. Drei Operatoren, die alle dasselbe Ziel verfolgen: Typen aus vorhandenem Code gewinnen, statt sie neu zu erfinden.
5:17 Der eine liefert die Schlüssel eines Typs, der zweite den Typ eines Werts, der dritte den Typ einer einzelnen Eigenschaft. Der praktische Gewinn steht im letzten Satz und ist beträchtlich: Ein abgeleiteter Typ bleibt automatisch korrekt, wenn sich seine Quelle ändert. Ein Bild: Statt die Zutatenliste abzuschreiben, verweisen Sie auf das Rezept. Ändert sich das Rezept, stimmt Ihre Liste weiterhin — weil es Ihre Liste gar nicht gibt.
5:43 Ein Auftragstyp mit zwei Feldern, und darunter drei Ableitungen, die alle keine eigene Pflege brauchen. Die erste kennt die Feldnamen, die zweite den Typ eines bestimmten Feldes, die dritte macht aus einem echten Wert einen Typ — das ist besonders praktisch bei Konfigurationsobjekten, die ohnehin schon dastehen. Und jetzt die Fußzeile, in der der eigentliche Gewinn steht: Kommt ein Feld zum Auftrag hinzu, wächst die Schlüsselunion automatisch mit.
6:08 Eine von Hand gepflegte Union täte das nicht — und wäre nach der dritten Änderung unvollständig, ohne dass es jemandem auffiele. Merken Sie sich vor allem die Richtung: Der erste Operator geht von einem Typ zu seinen Schlüsseln, der zweite von einem Wert zu seinem Typ — das ist die Brücke von der Wertwelt in die Typwelt.
6:26 Der dritte holt aus einem Typ den Typ einer benannten Eigenschaft. Und die letzte Zeile ist die elegante Kombination, die man sich merken sollte: Schlüssel holen und damit gleich alle Werttypen abfragen. Das ergibt die Union aller vorkommenden Werttypen — sehr nützlich, wenn Sie über eine Struktur laufen und wissen wollen, was Ihnen dabei begegnen kann.
Conditional Types und infer
6:46 Jetzt wird es anspruchsvoller: Fallunterscheidung auf Typebene. Der Compiler kann eine Bedingung prüfen — nicht über Werte, sondern über Typen — und danach zwischen zwei Ergebnissen wählen. Mit diesen Werkzeugen sind die eingebauten Hilfstypen der Standardbibliothek gebaut. Ein bedingter Typ wählt zwischen zwei Typen, abhängig davon, ob eine Bedingung auf Typebene zutrifft — es ist ein if auf der Typebene.
7:11 Und das Schlüsselwort für die Ableitung zieht dabei einen Teiltyp aus dem geprüften Typ heraus und macht ihn benutzbar. Ein Bild: Sie öffnen ein Paket, um festzustellen, was für ein Inhalt drin ist, und arbeiten dann mit genau diesem Inhalt weiter. Wichtig zur Einordnung: Sie werden das selten selbst schreiben, aber Sie werden es lesen — die Standardbibliothek besteht daraus.
7:32 Hier holen wir aus dem Ergebnistyp aus Modul 3 den eigentlichen Nutzwert heraus. Die Frage lautet: Ist das ein Ergebnis, das irgendetwas trägt? Wenn ja, liefere genau dieses Irgendetwas. Die beiden Zeilen darunter zeigen es in Aktion — ein Ergebnis mit Auftrag liefert den Auftrag, eine Zeichenkette liefert nichts. Und dieses „nichts" ist Absicht, das steht in der Fußzeile: Ein unpassender Typ soll kein plausibles Ergebnis liefern, sondern gar keines.
8:00 Wer hier stattdessen den abschaltenden Typ einsetzt, baut sich eine Ausnahme, die im Betrieb zuschlägt. Der erste Punkt ist das klassische Rätsel für alle, die damit anfangen: Ein bedingter Typ wirkt über eine Union hinweg gliedweise, nicht auf die Union als Ganzes. Er verteilt sich. Wer das nicht weiß, bekommt ein Ergebnis, das schlicht nicht zu passen scheint.
8:22 Der zweite: mehrere Ableitungen verschachteln und damit Fehlermeldungen erzeugen, die niemand mehr liest. Und der dritte ist eine Haltungsfrage, die ich unterstreichen möchte: bedingte Typen einsetzen, wo ein schlichter Overload verständlicher wäre. Klug ist nicht dasselbe wie gut.
Mapped und Template Literal Types
8:39 Zwei weitere Werkzeuge, die zusammen erstaunlich mächtig sind: Typen umformen und Namen zusammensetzen. Und hier begegnet uns eine der wenigen echten Verhaltensänderungen in TypeScript 7 — sie steht bei den Stolpersteinen und betrifft ausgerechnet Emojis. Ein umformender Typ läuft über alle Schlüssel eines bestehenden Typs und ändert je Eigenschaft etwas — alles optional machen, alles schreibgeschützt machen, alles in eine andere Form bringen.
9:05 Ein zusammensetzender Typ baut aus Zeichenketten-Typen neue zusammen, wie eine Schablone mit Platzhaltern. In Kombination lassen sich damit Namenskonventionen ausdrücken, die sonst von Hand gepflegt werden müssten — und Handpflege heißt eben immer auch: irgendwann vergessen. Oben die Umformung: über alle Schlüssel laufen und jede Eigenschaft optional machen — genau so ist der eingebaute Entwurfstyp gebaut, den Sie vermutlich schon benutzt haben.
9:30 Unten die Zusammensetzung: Aus einer Zustandsunion entstehen automatisch die passenden Ereignisnamen. Und jetzt der Gewinn, der in der Fußzeile steht und die Verbindung zu Modul 3 schlägt: Kommt ein Zustand zur Union hinzu, entsteht der passende Ereignisname von selbst. Niemand muss ihn nachtragen, niemand kann ihn vergessen, und ein Tippfehler ist nicht mehr möglich.
9:51 Der letzte Punkt ist der aktuelle und deshalb erwähnenswert: TypeScript 7 zerlegt Emojis in zusammengesetzten Zeichenketten-Typen nicht mehr in Halbzeichen. Das ist eine echte Verhaltensänderung — und wer sich, aus welchen Gründen auch immer, auf das alte Verhalten verlassen hat, merkt es bei der Migration. Die anderen beiden sind Maßfragen: Umformungen über Typen legen, deren Schlüssel gar nicht bekannt sind, bringt nichts.
10:15 Und Namenskonventionen auf Typebene bauen, die hinterher niemand mehr nachvollzieht — technisch beeindruckend, im Team ein Problem.
Utility Types, fertig und selbst gebaut
10:23 Die gute Nachricht nach so viel Typmaschinerie: Das meiste davon müssen Sie gar nicht selbst bauen. Die Standardbibliothek deckt den Alltag ab. Sehen wir uns an, was ohnehin da ist — und woran Sie erkennen, dass ein eigener Hilfstyp wirklich gebraucht wird. Fünf Hilfstypen, die den größten Teil des Alltags abdecken, und alle sind mit den Werkzeugen der letzten beiden Kapitel gebaut — Sie können jetzt in die Standardbibliothek schauen und verstehen, was dort passiert.
10:50 Der erste macht alles optional, für Entwürfe und Teilaktualisierungen. Die beiden nächsten wählen aus oder lassen weg, für Sichten auf eine Schnittstelle. Dann die Abbildung von Schlüsseln auf einen Werttyp, die uns in Modul 2 schon begegnet ist. Und der letzte leitet den Rückgabetyp einer Funktion ab, statt ihn ein zweites Mal hinzuschreiben — dasselbe Prinzip wie in Kapitel 3.
11:12 Oben der Standardfall, der wirklich fast immer reicht. Unten ein eigener — und die Fußzeile nennt die Faustregel, die ich Ihnen mitgeben möchte: Ein eigener Hilfstyp lohnt ab der dritten Verwendung. Vorher ist er verfrüht, nachher wird die Wiederholung lästig. Und dann braucht er einen Namen, der seine Absicht verrät, nicht seine Technik. „Tief lesbar" sagt, was gemeint ist.
11:35 Ein Name wie „Umformung zwei" sagt nichts und macht den Code schwerer, nicht leichter. Der Name ist bei Hilfstypen wichtiger als bei fast allem anderen. Drei gute Gründe und ein schlechter — und der schlechte steht am Ende, weil er der häufigste ist. Ein eigener Hilfstyp lohnt, wenn dieselbe Umformung mehrfach wortgleich im Code steht. Wenn sie einen fachlichen Namen hat und nicht nur eine technische Form.
12:00 Und wenn sie an Stellen gebraucht wird, die sich unabhängig voneinander ändern. Kein Grund ist dagegen, dass es kürzer aussieht als die Standardvariante. Kürze ist selten der Engpass. Verständlichkeit schon — und die Standardtypen kennt jeder im Team, den eigenen erst mal niemand.
Branded Types
12:17 Jetzt kommt der Einlöse-Moment aus Modul 2. Dort haben wir gesehen, dass drei fachlich verschiedene Kennungen vertauschbar sind, weil sie alle Zeichenketten sind und der Compiler nach Struktur vergleicht. Hier wenden wir genau diese Eigenschaft gegen sich selbst. TypeScript kennt keine nominale Typisierung — der Name eines Typs spielt für die Zuweisbarkeit keine Rolle, nur seine Struktur.
12:39 Ein Branded Type nähert Nominalität an, indem er an einen strukturellen Typ eine künstliche Markierung hängt, die von außen nicht erreichbar ist. Das Bild: Zwei Schlüssel mit identischen Zacken bekommen einen unsichtbaren Stempel, und das Schloss prüft den Stempel mit. Zwei Zeichenketten mit verschiedenen Marken sind damit nicht mehr austauschbar — obwohl zur Laufzeit beide schlicht Zeichenketten bleiben.
13:02 Sehen Sie sich die letzte Zeile an: Genau diese Zuweisung war in Modul 2 noch völlig in Ordnung, und jetzt ist sie ein Fehler. Der Aufbau darüber ist etwas technisch, aber das Prinzip ist schlicht: ein eindeutiger, von außen nicht erreichbarer Schlüssel, der als zusätzliche Eigenschaft mitgeführt wird. Und die Fußzeile schlägt den Bogen zurück zu Modul 1: Die Marke existiert nur beim Übersetzen. Zur Laufzeit bleibt es eine Zeichenkette, die Typlöschung gilt unverändert.
13:29 Sie haben also keine Laufzeitprüfung gewonnen — aber jede Verwechslung im Code fällt jetzt auf. Vier Situationen, und die dritte ist die eleganteste. Bei Kennungen, die zur Laufzeit gleich aussehen und nie vertauscht werden dürfen. Bei Werten mit Einheit — Cent gegen Euro, Sekunden gegen Millisekunden; die Raumfahrt hat mit genau dieser Art Verwechslung schon Sonden verloren.
13:52 Und dann der dritte: Ein geprüfter Wert bekommt eine Marke, ein ungeprüfter nicht. Damit ist im Typ sichtbar, was die Validierung passiert hat — das wird uns in Modul 6 wieder begegnen. Nicht lohnend ist der Aufwand bei internen Hilfstypen, die eine Datei nie verlassen.
Grenzen komplexer Typkonstruktionen
14:09 Zum Abschluss die ehrliche Frage nach dem Maß. Denn all diese Werkzeuge haben einen Preis — an Prüfzeit, an Lesbarkeit, an Verständlichkeit der Fehlermeldungen. Und hier kommt auch die nüchternste Zahl des ganzen Seminars zur Sprache. Jede Typkonstruktion kostet drei Dinge: Prüfzeit, Lesbarkeit und die Qualität der Fehlermeldungen.
14:29 TypeScript 7 senkt die Kosten der Maschinerie erheblich — der Motor ist schneller. Was es nicht senkt, ist der algorithmische Aufwand einer entarteten Konstruktion. Der Satz, den ich Ihnen mitgeben möchte, steht am Ende: Wer sie geschrieben hat, wartet weiter — nur schneller. Ein schnelleres Auto macht die Umleitung nicht kürzer.
14:49 Das ist keine Kritik an Version 7, das ist einfach die richtige Erwartungshaltung. Diese beiden Zeilen gehören zu den wichtigsten des Seminars, weil sie eine Erwartung geraderücken. Oben die Messung des Teams am Editor-Projekt: knapp Faktor zwölf. Darunter eine Messung aus einem echten größeren Projekt: rund Faktor drei. Beide Zahlen sind richtig — und sie widersprechen sich nicht.
15:12 Die Fußzeile nennt den Grund, und das Projekt nennt ihn selbst: Nur ein Teil der Bauzeit steckte je in der Typprüfung, der Rest in Bündeln, Tests und Codegenerierung. Wenn Sie ein Drittel schneller machen, wird das Ganze eben nicht zwölfmal schneller. Vier Warnzeichen, und das letzte ist das eigentlich entscheidende. Auffällig lange Prüfzeit an wenigen, kleinen Dateien — das deutet fast immer auf eine entartete Konstruktion.
15:38 Fehlermeldungen über mehrere Bildschirmhöhen. Ein Abbruch wegen zu tiefer Typinstanziierung, also die harte Grenze des Compilers. Und dann der vierte, menschliche: Im Review liest die Meldung niemand mehr. Das ist die praktische Grenze, und sie liegt deutlich unterhalb der technischen. Ein Typ, dessen Fehlermeldung niemand versteht, hilft niemandem mehr — er kostet nur.
16:01 Der erste Punkt ist eine Warnung für die Kommunikation nach oben: Faktor zehn im Unternehmen versprechen und Faktor drei liefern. Beide Zahlen sind belegt, aber die Erwartung setzen Sie. Versprechen Sie lieber „wir messen" als „es wird zehnmal schneller". Der zweite: die Typebene für ein Problem einsetzen, das eine Laufzeitprüfung besser löst — dazu morgen mehr.
16:23 Und der dritte ist der schwerste, weil er Mut braucht: eine Konstruktion behalten, weil sie funktioniert, obwohl niemand sie versteht. Funktionierender Code, den niemand ändern kann, ist trotzdem ein Problem.
Übung — eine Repository-API bauen
16:36 Jetzt setzen Sie das Gelernte an einer sehr typischen Aufgabe ein: vier Entitäten, dieselben Operationen, und die konkreten Typen sollen erhalten bleiben. Das ist der Klassiker unter den Generics-Anwendungen — und die Übung hat ein Bewertungskriterium, das Sie überraschen wird. Das Lernziel: eine generische Schnittstelle so entwerfen, dass sie für mehrere Entitäten gilt und die konkreten Typen dabei erhält.
17:00 Erfolgreich sind Sie unter zwei Bedingungen — und die zweite ist die ungewöhnliche. Erstens: Kein Aufrufer braucht eine Behauptung, um mit den Ergebnissen zu arbeiten. Und zweitens: Eine absichtlich falsche Verwendung erzeugt eine Meldung, die im Review verständlich ist. Die Fehlermeldung ist Teil Ihrer Schnittstelle. Wer früh fertig ist, führt Marken für die drei Kennungen ein und schaut, welche Aufrufe dadurch zu Fehlern werden.
17:26 Fünf Schritte, und der rote Faden lautet: erst die Fachlichkeit, dann die Technik, dann die Probe aufs Exempel. Zuerst sammeln Sie die gemeinsamen Operationen, bevor irgendein Typ entsteht. Dann formulieren Sie das Constraint: Was muss eine Entität mindestens haben? Danach schreiben Sie die Schnittstelle generisch und reichen die Rückgaben durch.
17:46 Und dann kommt der eigentliche Test: Schreiben Sie absichtlich eine falsche Verwendung und lesen Sie die Meldung. Schritt fünf entscheidet die Übung — versteht sie jemand ohne den Typcode? Eine unverständliche Meldung ist ein Befund, kein Schönheitsfehler. Vier Sätze zum Mitnehmen. Ein Typparameter hält offen, der abschaltende Typ gibt auf — das ist nicht dasselbe, auch wenn es im Editor kurz gleich bequem aussieht.
18:11 Die Operatoren für Schlüssel und Werttypen holen Typinformation, statt sie ein zweites Mal zu schreiben. Marken trennen, was strukturell gleich aussieht und fachlich nicht dasselbe ist. Und die Grenze setzt nicht der Compiler, sondern die lesbare Fehlermeldung. Im nächsten Modul wechseln wir die Seite: Was passiert, wenn die Daten gar nicht aus unserem Code kommen?
Lieber mit Trainer? Dieses Modul ist Teil unserer Team-Schulungen — mit Übungen, Ihrem eigenen Code und Fragen, die ein Video nicht beantwortet. Mehr erfahren →