Der Autor des Artikels, den wir heute übersetzen, sagt, TypeScript sei großartig. Als er anfing, TS zu benutzen, mochte er die Freiheit, die dieser Sprache innewohnt. Je mehr Aufwand ein Programmierer in seine Arbeit mit TS-spezifischen Mechanismen steckt, desto größer sind die Vorteile, die er erhält. Dann verwendete er nur periodisch Typanmerkungen. Manchmal nutzte er die Möglichkeiten zur Code-Vervollständigung und zu Compiler-Hinweisen, stützte sich jedoch hauptsächlich auf seine eigene Vision der von ihm gelösten Aufgaben.
Im Laufe der Zeit stellte der Autor dieses Materials fest, dass er jedes Mal, wenn er Fehler umgeht, die bei der Kompilierung festgestellt wurden, eine Zeitbombe in seinen Code einfügt, die während der Programmausführung explodieren kann. Jedes Mal, wenn er mit einem einfachen Konstrukt mit Fehlern „kämpfte“, musste er dafür mit vielen Stunden hartem Debuggen bezahlen.

Infolgedessen kam er zu dem Schluss, dass es besser ist, dies nicht zu tun. Er freundete sich mit dem Compiler an und begann, auf seine Hinweise zu achten. Der Compiler findet Probleme im Code und meldet sie, lange bevor sie echten Schaden anrichten können. Der Autor des Artikels, der sich selbst als Entwickler betrachtete, erkannte, dass der Compiler sein bester Freund ist, da er ihn vor sich selbst schützt. Wie kann man sich nicht an die Worte von Albus Dumbledore erinnern: "Es erfordert viel Mut, sich Ihren Feinden zu widersetzen, aber nicht weniger, als sich Ihren Freunden zu widersetzen."
Egal wie gut der Compiler sein mag, es ist nicht immer einfach zu gefallen. Manchmal ist es sehr schwierig, die Verwendung eines 
any Typs zu vermeiden. Und manchmal scheint es, dass 
any die einzig vernünftige Lösung für ein Problem ist.
Dieses Material konzentriert sich auf zwei Situationen. Indem Sie die Verwendung eines 
any Typs vermeiden, können Sie die Typensicherheit des Codes gewährleisten, die Möglichkeiten für dessen Wiederverwendung eröffnen und ihn intuitiv gestalten.
Generika
Angenommen, wir arbeiten an einer Datenbank einer Schule. Wir haben eine sehr praktische 
getBy . Um das Objekt zu erhalten, das den Schüler mit seinem Namen darstellt, können wir einen Befehl der Form 
getBy(model, "name", "Harry") . Werfen wir einen Blick auf die Implementierung dieses Mechanismus (um den Code nicht zu komplizieren, wird die Datenbank durch ein gewöhnliches Array dargestellt).
 type Student = { name: string; age: number; hasScar: boolean; }; const students: Student[] = [ { name: "Harry", age: 17, hasScar: true }, { name: "Ron", age: 17, hasScar: false }, { name: "Hermione", age: 16, hasScar: false } ]; function getBy(model, prop, value) {   return model.filter(item => item[prop] === value)[0] } 
Wie Sie sehen können, haben wir eine gute Funktion, aber sie verwendet keine Typanmerkungen, und ihre Abwesenheit bedeutet auch, dass eine solche Funktion nicht als typsicher bezeichnet werden kann. Repariere es.
 function getBy(model: Student[], prop: string, value): Student | null {   return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "name", "Hermione") // result: Student 
Unsere Funktion sieht also schon viel besser aus. Der Compiler kennt jetzt die Art des von ihm erwarteten Ergebnisses, dies wird später nützlich sein. Um jedoch ein sicheres Arbeiten mit Typen zu erreichen, haben wir die Möglichkeiten der Wiederverwendung der Funktion geopfert. Was ist, wenn wir es jemals brauchen, um andere Entitäten zu bekommen? Es kann nicht sein, dass diese Funktion in keiner Weise verbessert werden konnte. Und das ist es wirklich.
In TypeScript können wir wie in anderen stark typisierten Sprachen Generika verwenden, die auch als "generische Typen", "universelle Typen" und "Verallgemeinerungen" bezeichnet werden.
Ein Generikum ähnelt einer regulären Variablen, enthält jedoch anstelle eines Werts eine Typdefinition. Wir schreiben den Code unserer Funktion so um, dass anstelle des 
Student Typs der universelle Typ 
T function getBy<T>(model: T[], prop: string, value): T | null {   return model.filter(item => item[prop] === value)[0] } const result = getBy<Student>(students, "name", "Hermione") // result: Student 
Schönheit! Jetzt ist die Funktion ideal für die Wiederverwendung, während die Typensicherheit noch auf unserer Seite steht. Beachten Sie, wie der 
Student Typ in der letzten Zeile des obigen Code-Snippets, in dem das generische 
T wird, explizit festgelegt 
T . Dies geschieht, um das Beispiel so klar wie möglich zu gestalten. Der Compiler kann jedoch unabhängig den erforderlichen Typ ableiten. In den folgenden Beispielen werden solche Typverfeinerungen daher nicht durchgeführt.
Jetzt haben wir eine zuverlässige Hilfsfunktion, die zur Wiederverwendung geeignet ist. Es kann jedoch noch verbessert werden. Was ist, wenn bei der Eingabe des zweiten Parameters ein Fehler auftritt und anstelle von 
"name" "naem" ? Die Funktion verhält sich so, als ob sich der gesuchte Schüler einfach nicht in der Datenbank befindet und, was am unangenehmsten ist, keine Fehler erzeugt. Dies kann zu einem langfristigen Debugging führen.
Zum Schutz vor solchen Fehlern führen wir einen anderen universellen Typ ein, 
P In diesem Fall ist es erforderlich, dass 
P ein Schlüssel vom Typ 
T ist. Wenn hier 
Student verwendet wird, ist es daher erforderlich, dass 
P die Zeichenfolge 
"name" , 
"age" oder 
"hasScar" . Hier erfahren Sie, wie es geht.
 function getBy<T, P extends keyof T>(model: T[], prop: P, value): T | null {   return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "naem", "Hermione") // Error: Argument of type '"naem"' is not assignable to parameter of type '"name" | "age" | "hasScar"'. 
Die Verwendung von Generika und dem 
keyof ist ein sehr mächtiger Trick. Wenn Sie Programme in eine IDE schreiben, die TypeScript unterstützt, können Sie durch Eingabe von Argumenten die Funktionen zur automatischen Vervollständigung nutzen, was sehr praktisch ist.
Wir haben die Arbeit an der 
getBy Funktion jedoch noch nicht abgeschlossen. Sie hat ein drittes Argument, dessen Art wir noch nicht festgelegt haben. Das passt uns überhaupt nicht. Bisher konnten wir nicht im Voraus wissen, um welchen Typ es sich handeln sollte, da dies davon abhängt, was wir als zweites Argument übergeben. Da wir nun Typ 
P , können wir den Typ für das dritte Argument dynamisch ableiten. Der Typ des dritten Arguments ist letztendlich 
T[P] . Wenn 
T Student und 
P "age" , hat 
T[P] die Typennummer.
 function getBy<T, P extends keyof T>(model: T[], prop: P, value: T[P]): T | null {   return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "age", "17") // Error: Argument of type '"17"' is not assignable to parameter of type 'number'. const anotherResult = getBy(students, "hasScar", "true") // Error: Argument of type '"true"' is not assignable to parameter of type 'boolean'. const yetAnotherResult = getBy(students, "name", "Harry") //      
Ich hoffe, dass Sie jetzt ein absolut klares Verständnis für die Verwendung von Generika in TypeScript haben. Wenn Sie jedoch mit allem, was Sie mit dem hier beschriebenen Code experimentieren möchten, sehr gut experimentieren möchten, können Sie hier einen Blick darauf werfen.
Bestehende Typen erweitern
Manchmal müssen wir Daten oder Funktionen zu Schnittstellen hinzufügen, deren Code wir nicht ändern können. Möglicherweise müssen Sie das Standardobjekt ändern, z. B. dem 
window eine Eigenschaft hinzufügen oder das Verhalten einer externen Bibliothek wie 
Express . In beiden Fällen können Sie das Objekt, mit dem Sie arbeiten möchten, nicht direkt beeinflussen.
Wir werden eine Lösung für dieses Problem suchen, indem wir die bereits bekannte Funktion 
getBy zum 
Array Prototyp hinzufügen. Auf diese Weise können wir mit dieser Funktion genauere syntaktische Konstruktionen erstellen. Im Moment sprechen wir nicht darüber, ob es gut oder schlecht ist, Standardobjekte zu erweitern, da unser Hauptziel darin besteht, den betrachteten Ansatz zu untersuchen.
Wenn wir versuchen, dem 
Array Prototyp eine Funktion hinzuzufügen, wird dem Compiler dies nicht sehr gefallen:
 Array.prototype.getBy = function <T, P extends keyof T>(   this: T[],   prop: P,   value: T[P] ): T | null { return this.filter(item => item[prop] === value)[0] || null; }; // Error: Property 'getBy' does not exist on type 'any[]'. const bestie = students.getBy("name", "Ron"); // Error: Property 'getBy' does not exist on type 'Student[]'. const potionsTeacher = (teachers as any).getBy("subject", "Potions") //  ...   ? 
Wenn wir versuchen, den Compiler zu beruhigen, indem wir das Konstrukt regelmäßig 
as any Konstrukt verwenden, werden wir alles, was wir erreicht haben, aufheben. Der Compiler ist stumm, aber Sie können die sichere Arbeit mit Typen vergessen.
Es wäre besser, den 
Array Typ zu erweitern, aber bevor wir dies tun, wollen wir uns damit befassen, wie TypeScript Situationen behandelt, in denen zwei Schnittstellen desselben Typs im Code vorhanden sind. Hier wird ein einfaches Aktionsschema angewendet. Anzeigen werden nach Möglichkeit kombiniert. Wenn Sie sie nicht kombinieren können, gibt das System einen Fehler aus.
Dieser Code funktioniert also:
 interface Wand { length: number } interface Wand {   core: string } const myWand: Wand = { length: 11, core: "phoenix feather" }  
Und dieser ist nicht:
 interface Wand { length: number } interface Wand {   length: string }  
Nachdem wir uns damit befasst haben, sehen wir, dass wir vor einer ziemlich einfachen Aufgabe stehen. Wir müssen 
getBy die 
Array<T> -Schnittstelle deklarieren und die Funktion 
getBy hinzufügen.
 interface Array<T> {  getBy<P extends keyof T>(prop: P, value: T[P]): T | null; } Array.prototype.getBy = function <T, P extends keyof T>(   this: T[],   prop: P,   value: T[P] ): T | null { return this.filter(item => item[prop] === value)[0] || null; }; const bestie = students.getBy("name", "Ron"); //   ! const potionsTeacher = (teachers as any).getBy("subject", "Potions") //     
Beachten Sie, dass der größte Teil des Codes, den Sie wahrscheinlich in Moduldateien schreiben, erforderlich ist. Um Änderungen an der 
Array Schnittstelle vorzunehmen, benötigen Sie Zugriff auf den globalen Bereich. Sie können dies tun, indem Sie die Typdefinition in 
declare global . Zum Beispiel so:
 declare global {   interface Array<T> {       getBy<P extends keyof T>(prop: P, value: T[P]): T | null;   } } 
Wenn Sie die Schnittstelle einer externen Bibliothek erweitern möchten, benötigen Sie höchstwahrscheinlich Zugriff auf den 
namespace dieser Bibliothek. Hier ist ein Beispiel für das Hinzufügen des 
userId zu 
Request aus der 
Express Bibliothek:
 declare global { namespace Express {   interface Request {     userId: string;   } } } 
Sie können hier mit dem Code in diesem Abschnitt experimentieren.
Zusammenfassung
In diesem Artikel haben wir uns mit Techniken zur Verwendung von Generika und Typerweiterungen in TypeScript befasst. Wir hoffen, dass das, was Sie heute gelernt haben, Ihnen hilft, zuverlässigen, verständlichen und typsicheren Code zu schreiben.
Liebe Leser! Wie stehen Sie zu einem Typ in TypeScript?
