Generics bieten wiederverwendbare Codestücke, die mit einer Reihe von Typen anstelle eines einzelnen Typs arbeiten. Generics bieten eine Möglichkeit, den Typ als Variable zu behandeln und ihn bei der Verwendung anzugeben, ähnlich wie Funktionsparameter.
Generika können in Verbindung mit Funktionen (Erstellung einer generischen Funktion), Klassen (Erstellung einer generischen Klasse) und Schnittstellen (Erstellung einer generischen Schnittstelle) verwendet werden.
Grundlegende Verwendung
Wahrscheinlich haben Sie in der Vergangenheit Generika verwendet, ohne es zu wissen - die häufigste Verwendung von Generika ist die Deklaration eines Arrays:
const myArray: string[];
Auf den ersten Blick ist es nichts Besonderes, wir erklären nur, dass meinArray
als ein Array von Zeichenketten, aber es ist dasselbe wie eine generische Deklaration:
const myArray: Array;
Dinge explizit halten
Lassen Sie uns mit einem sehr einfachen Beispiel beginnen - wie könnten wir diese Vanilla-JS-Funktion auf TypeScript portieren:
function getPrefiledArray(filler, length) {
return (new Array(length)).fill(filler);
}
Diese Funktion gibt ein Array zurück, das mit einer bestimmten Anzahl von Füllstoff
also Länge
wird Nummer
und die ganze Funktion wird ein Array von Füllstoff
- aber was ist Füllmaterial? An dieser Stelle kann es alles Mögliche sein, eine Möglichkeit ist die Verwendung von jede
:
function getPrefiledArray(filler: any, length: number): any[] {
return (new Array(length)).fill(filler);
}
Verwendung von jede
ist sicherlich generisch - wir können buchstäblich alles übergeben, so dass "mit einer Anzahl von Typen statt mit einem einzigen Typ arbeiten" aus der Definition vollständig abgedeckt ist, aber wir verlieren die Verbindung zwischen Füllstoff
Typ und den Rückgabetyp. In diesem Fall wollen wir etwas Gemeinsames zurückgeben, und wir können dieses Gemeinsame explizit definieren als Typparameter:
function getPrefiledArray(filler: T, length: number): T[] {
return (new Array(length)).fill(filler);
}
und verwenden Sie es so:
const prefilledArray = getPrefiledArray(0, 10);
Allgemeine Beschränkungen
Schauen wir uns andere, wahrscheinlich häufigere Fälle an. Warum verwenden wir eigentlich Typen in Funktionen? Für mich ist es, um sicherzustellen, dass die an die Funktion übergebenen Argumente einige Eigenschaften haben, mit denen ich interagieren möchte.
Lassen Sie uns noch einmal versuchen, eine einfache Vanilla-JS-Funktion nach TS zu portieren.
function getLength(thing) {
return thing.length;
}
Wir haben ein nicht-triviales Rätsel - wie kann man sicherstellen, dass das Ding eine Länge
Eigenschaft, und der erste Gedanke könnte sein, etwas wie zu tun:
function getLength(thing: typeof Array):number {
return thing.length;
}
und je nach Kontext mag das richtig sein, insgesamt sind wir ein wenig generisch - es wird mit Arrays verschiedener Typen funktionieren, aber was ist, wenn wir nicht wirklich wissen, ob das Ding immer ein Array sein sollte - vielleicht ist das Ding ein Fußballfeld oder eine Bananenschale? In diesem Fall müssen wir die allgemeinen Eigenschaften des Objekts in einem Konstrukt zusammenfassen, das die Eigenschaften eines Objekts definieren kann - einer Schnittstelle:
Schnittstelle IThingWithLength {
Länge: Zahl;
}
Wir können verwenden IThingWithLength
Schnittstelle als Typ der Sache
Parameter:
function getLength(thing: IThingWithLength):number {
return thing.length;
}
In diesem einfachen Beispiel ist das völlig in Ordnung, aber wenn wir diesen Typ generisch halten wollen und nicht das Problem aus dem ersten Beispiel haben, können wir Allgemeine Beschränkungen:
function getLength(thing: T):number {
return thing.length;
}
und verwenden Sie es:
Schnittstelle IBananaPeel {
Dicke: Zahl;
Länge: Zahl;
}
const bananaPeel: IBananaPeel = {Dicke: 0.2, Länge: 3.14};
getLength(bananaPeel);
Verwendung von erweitert
gewährleistet, dass T
werden Eigenschaften enthalten, die durch IThingWithLength
.
Generische Klassen
Bis zu diesem Punkt haben wir mit generischen Funktionen gearbeitet, aber das ist nicht der einzige Ort, an dem generische Funktionen glänzen. Wir wollen sehen, wie wir sie in Klassen einbauen können.
Versuchen wir zunächst, ein Bündel Bananen im Bananenkorb zu lagern:
Klasse Banane {
constructor(
public length: Zahl,
public color: string,
public ionizingRadiation: Zahl
) {}
}
class BananaBasket {
private bananas: Banana[] = [];
add(banana: Banana): void {
this.bananas.push(banana);
}
}
const bananaBasket = new BananaBasket();
bananaBasket.add(new Banana(3.14, 'red', 10e-7));
Versuchen wir nun, einen Allzweckkorb zu erstellen, für verschiedene Dinge desselben Typs:
Klasse Basket {
private stuff: T[] = [];
add(thing: T): void {
this.stuff.push(thing);
}
}
const bananaBasket = new Basket();
Und schließlich nehmen wir an, dass unser Korb ein Behälter für radioaktives Material ist und wir nur Materie lagern können, die ionisierendeStrahlung
Eigentum:
Schnittstelle IRadioactive {
ionisierendeStrahlung: Nummer;
}
class RadioactiveContainer {
private stuff: T[] = [];
add(thing: T): void {
this.stuff.push(thing);
}
}
Generische Schnittstelle
Zum Schluss wollen wir versuchen, all unser Wissen zu sammeln und ein radioaktives Imperium zu errichten, das auch generische Schnittstellen verwendet:
// Gemeinsame Attribute für Container definieren
Schnittstelle IRadioactive {
ionisierendeStrahlung: Zahl;
}
// Definieren Sie etwas, das radioaktiv ist
interface IBanana extends IRadioactive {
Länge: Zahl;
Farbe: string;
}
// Definieren Sie etwas, das nicht radioaktiv ist
Schnittstelle IDog {
Gewicht: Zahl;
}
// Definiere Schnittstelle für Container, die nur radioaktives Material aufnehmen können
interface IRadioactiveContainer {
add(thing: T): void;
getRadioactiveness():number;
}
// Definieren Sie eine Klasse, die die Schnittstelle des radioaktiven Containers implementiert
class RadioactiveContainer implements IRadioactiveContainer {
private stuff: T[] = [];
add(thing: T): void {
this.stuff.push(thing);
}
getRadioactiveness(): number {
return this.stuff.reduce((a, b) => a + b.ionisierendeStrahlung, 0)
}
}
// ERROR! Typ 'IDog' erfüllt nicht die Einschränkung 'IRadioactive'
// Und es ist ziemlich brutal, Hunde in radioaktiven Containern zu speichern
const dogsContainer = new RadioactiveContainer();
// Alles gut, Familie!
const radioactiveContainer = new RadioactiveContainer();
// Denkt daran, euren radioaktiven Abfall zu sortieren - erstellt einen separaten Behälter nur für Bananen
const bananasContainer = new RadioactiveContainer();
Das ist alles, Leute!