Generika in TypeScript: Zusammenkommen

Hallo allerseits! Das TestMace- Team veröffentlicht eine weitere Übersetzung des Artikels aus der Welt der Webentwicklung. Diesmal für Anfänger! Viel Spaß beim Lesen.


Zerschmettere den Schleier des Geheimnisses und des Missverständnisses über die <T> -Syntax und freundete dich schließlich damit an



Wahrscheinlich schlagen nur erfahrene Entwickler von Java oder anderen stark typisierten Sprachen nicht die Augen, wenn sie das Generikum in TypeScript sehen. Die Syntax unterscheidet sich grundlegend von allem, was wir in JavaScript gewohnt sind. Daher ist es nicht so einfach, sofort zu erraten, was es überhaupt tut.


Ich möchte Ihnen zeigen, dass tatsächlich alles viel einfacher ist, als es scheint. Ich werde beweisen, dass Sie Generika ohne zusätzlichen Aufwand verwenden können, wenn Sie eine Funktion mit Argumenten in JavaScript implementieren können. Lass uns gehen!


Generika in TypeScript


Die TypeScript-Dokumentation enthält die folgende Definition: "Generika sind die Möglichkeit, Komponenten zu erstellen, die nicht nur mit einem, sondern mit mehreren Datentypen funktionieren."


Wow! Die Hauptidee ist also, dass Generika es uns ermöglichen, einige wiederverwendbare Komponenten zu erstellen, die mit verschiedenen Arten von Daten arbeiten, die an sie übertragen werden. Aber wie ist das möglich? Hier ist was ich denke.


Generika und Typen sind wie Funktionswerte und Argumente miteinander verbunden. Auf diese Weise können Sie Komponenten (Funktionen, Klassen oder Schnittstellen) mitteilen, welchen Typ sie beim Aufrufen verwenden sollen. Genau wie bei einem Aufruf teilen wir der Funktion mit, welche Werte als Argumente verwendet werden sollen.


Es ist am besten, dies am Beispiel einer generischen Funktion zu verstehen. Eine identische Funktion ist eine Funktion, die den Wert des an sie übergebenen Arguments zurückgibt. In JavaScript sieht es folgendermaßen aus:


identity.js
 function identity (value) { return value; } console.log(identity(1)) // 1 

Lassen Sie es mit Zahlen funktionieren:


identity.ts
 function identity (value: Number) : Number { return value; } console.log(identity(1)) // 1 

Nun, wir haben der Definition einer identischen Funktion einen Typ hinzugefügt, aber wir möchten, dass diese flexibler ist und für Werte aller Art und nicht nur für Zahlen funktioniert. Dafür sind Generika da. Sie ermöglichen es der Funktion, Werte jeder Art von Daten am Eingang zu übernehmen und abhängig von ihnen die Funktion selbst zu transformieren.


genericIdentity.ts
 function identity <T>(value: T) : T { return value; } console.log(identity<Number>(1)) // 1 

Oh, diese seltsame <T> -Syntax! Stoppen Sie die Panik. Wir übergeben nur den Typ, den wir für einen bestimmten Funktionsaufruf verwenden möchten.



Schauen Sie sich das Bild oben an. Wenn Sie die identity<Number>(1) aufrufen, ist der Number dasselbe Argument wie 1. Er wird überall durch T Eine Funktion kann mehrere Typen auf dieselbe Weise annehmen wie mehrere Argumente.



Schauen Sie sich den Funktionsaufruf an. Jetzt sollte Sie die generische Syntax nicht erschrecken. T und U sind nur die Namen der Variablen, die Sie selbst zuweisen. Wenn eine Funktion aufgerufen wird, werden stattdessen die Typen angegeben, mit denen diese Funktion funktioniert .


Eine alternative Version des Verständnisses des Konzepts von Generika besteht darin, dass sie die Funktion in Abhängigkeit vom angegebenen Datentyp transformieren. Die folgende Animation zeigt, wie sich der Funktionsdatensatz und das zurückgegebene Ergebnis ändern, wenn der Typ geändert wird.



Wie Sie sehen, akzeptiert die Funktion jeden Typ, sodass Sie wiederverwendbare Komponenten verschiedener Typen erstellen können, wie in der Dokumentation versprochen.


Achten Sie besonders auf den zweiten Aufruf von console.log in der obigen Animation - der Typ wird nicht an ihn übergeben. In diesem Fall versucht TypeScript, den Typ aus den übertragenen Daten zu berechnen.


Generische Klassen und Schnittstellen


Sie wissen bereits, dass Generika nur eine Möglichkeit sind, Typen an eine Komponente zu übergeben. Sie haben gerade gesehen, wie sie mit Funktionen arbeiten, und ich habe gute Nachrichten: Sie funktionieren genauso mit Klassen und Schnittstellen. In diesem Fall folgt die Typangabe dem Namen der Schnittstelle oder Klasse.


Schauen Sie sich ein Beispiel an und versuchen Sie es selbst herauszufinden. Ich hoffe es ist dir gelungen.


genericClass.ts
 interface GenericInterface<U> { value: U getIdentity: () => U } class IdentityClass<T> implements GenericInterface<T> { value: T constructor(value: T) { this.value = value } getIdentity () : T { return this.value } } const myNumberClass = new IdentityClass<Number>(1) console.log(myNumberClass.getIdentity()) // 1 const myStringClass = new IdentityClass<string>("Hello!") console.log(myStringClass.getIdentity()) // Hello! 

Wenn der Code nicht sofort verstanden wird, versuchen Sie, die Typwerte von oben nach unten bis zu den Funktionsaufrufen zu verfolgen. Das Verfahren ist wie folgt:


  1. Eine neue Instanz der IdentityClass Klasse wird erstellt und der Number und der Wert 1 werden an sie übergeben.
  2. In der Klasse wird dem Wert von T der Typ Number zugewiesen.
  3. IdentityClass implementiert GenericInterface<T> , und wir wissen, dass T Number , und ein solcher Datensatz entspricht GenericInterface<Number> .
  4. In GenericInterface generisches U zu Number . In diesem Beispiel habe ich absichtlich verschiedene Variablennamen verwendet, um zu zeigen, dass der Typwert die Kette hinaufgeht und der Variablenname keine Bedeutung hat.

Echte Anwendungsfälle: Gehen Sie über primitive Typen hinaus


In allen oben genannten Codeeinfügungen wurden primitive Typen wie Number und string verwendet. Zum Beispiel ist dies das Meiste, aber in der Praxis ist es unwahrscheinlich, dass Sie Generika für primitive Typen verwenden. Generika sind sehr nützlich, wenn Sie mit beliebigen Typen oder Klassen arbeiten, die einen Vererbungsbaum bilden.


Betrachten Sie ein klassisches Beispiel für Vererbung. Nehmen wir an, wir haben eine Car , die die Grundlage für die Truck und Vespa Klassen bildet. Wir schreiben die WashCar-Dienstprogrammfunktion, die eine generische Instanz von Car und zurückgibt.


car.ts
 class Car { label: string = 'Generic Car' numWheels: Number = 4 horn() { return "beep beep!" } } class Truck extends Car { label = 'Truck' numWheels = 18 } class Vespa extends Car { label = 'Vespa' numWheels = 2 } function washCar <T extends Car> (car: T) : T { console.log(`Received a ${car.label} in the car wash.`) console.log(`Cleaning all ${car.numWheels} tires.`) console.log('Beeping horn -', car.horn()) console.log('Returning your car now') return car } const myVespa = new Vespa() washCar<Vespa>(myVespa) const myTruck = new Truck() washCar<Truck>(myTruck) 

Indem wir washCar Funktion washCar , dass T extends Car , geben wir an, welche Funktionen und Eigenschaften wir in dieser Funktion verwenden können. Mit Generic können Sie auch Daten des angegebenen Typs anstelle des üblichen Car .


Das Ergebnis dieses Codes ist:


 Received a Vespa in the car wash. Cleaning all 2 tires. Beeping horn - beep beep! Returning your car now Received a Truck in the car wash. Cleaning all 18 tires. Beeping horn - beep beep! Returning your car now 

Zusammenfassend


Ich hoffe, ich habe Ihnen beim Umgang mit Generika geholfen. Denken Sie daran, Sie müssen nur den Typwert an die Funktion übergeben :)


Wenn Sie mehr über Generika lesen möchten, habe ich unten einige Links angehängt.


Was zu lesen :


Source: https://habr.com/ru/post/de455473/


All Articles