Das Schlüsselwort „extens“ in TypeScript ist eine Art Schweizer Taschenmesser. Es wird in mehreren Kontexten verwendet, einschließlich Vererbung, Generika und bedingten Typen. Wenn Sie wissen, wie Sie Erweiterungen effektiv nutzen, können Sie robusteren, wiederverwendbareren und typsichereren Code erstellen.
Vererbung mit Extends
Eine der Hauptanwendungen von Extends ist die Vererbung, die es Ihnen ermöglicht, neue Schnittstellen oder Klassen zu erstellen, die auf vorhandenen aufbauen.
interface User {
firstName: string;
lastName: string;
email: string;
}
interface StaffUser extends User {
roles: string[];
department: string;
}
const regularUser: User = {
firstName: "John",
lastName: "Doe",
email: "john@example.com"
};
const staffMember: StaffUser = {
firstName: "Jane",
lastName: "Smith",
email: "jane@company.com",
roles: ["Manager", "Developer"],
department: "Engineering"
};
In diesem Beispiel erweitert StaffUser User, erbt alle seine Eigenschaften und fügt neue hinzu. Dadurch können wir spezifischere Typen basierend auf allgemeineren erstellen.
Klassenvererbung
Das Schlüsselwort „extends“ wird auch für die Klassenvererbung verwendet:
class Animal {
constructor(public name: string) {}
makeSound(): void {
console.log("Some generic animal sound");
}
}
class Dog extends Animal {
constructor(name: string, public breed: string) {
super(name);
}
makeSound(): void {
console.log("Woof! Woof!");
}
fetch(): void {
console.log(`${this.name} is fetching the ball!`);
}
}
const myDog = new Dog("Buddy", "Golden Retriever");
myDog.makeSound(); // Output: Woof! Woof!
myDog.fetch(); // Output: Buddy is fetching the ball!
Hier erweitert Dog Animal, erbt dessen Eigenschaften und Methoden und fügt auch eigene hinzu.
Typbeschränkungen in Generics
Das Schlüsselwort „extens“ ist bei der Arbeit mit Generika von entscheidender Bedeutung, da es uns ermöglicht, die Typen einzuschränken, die mit einer generischen Funktion oder Klasse verwendet werden können.
interface Printable {
print(): void;
}
function printObject<T extends Printable>(obj: T) {
obj.print();
}
class Book implements Printable {
print() {
console.log("Printing a book.");
}
}
class Magazine implements Printable {
print() {
console.log("Printing a magazine.");
}
}
const myBook = new Book();
const myMagazine = new Magazine();
printObject(myBook); // Output: Printing a book.
printObject(myMagazine); // Output: Printing a magazine.
// printObject(42); // Error, number doesn't have a 'print' method
-
Schnittstelle Printable: Hier definieren wir eine Schnittstelle namens Printable. Diese Schnittstelle deklariert einen Vertrag, den jede Klasse, die sie implementiert, einhalten muss. Der Vertrag legt fest, dass jede Klasse, die Printable implementiert, eine Methode namens print bereitstellen muss, die keine Argumente akzeptiert und void zurückgibt
-
Funktion printObject(obj: T): Dies ist eine generische Funktion namens printObject. Es benötigt ein einzelnes Argument namens obj, das vom Typ T ist. Der Typparameter T ist auf Typen beschränkt, die die Printable-Schnittstelle erweitern (implementieren) und als Argument für diese Funktion verwendet werden können.
-
Die Klasse Book implementiert Printable und die Klasse Magazine implementiert Printable: Hier definieren wir zwei Klassen, Book und Magazine, die beide die Printable-Schnittstelle implementieren. Dies bedeutet, dass diese Klassen eine Druckmethode bereitstellen müssen, wie im Vertrag der Printable-Schnittstelle erforderlich.
-
const myBook = new Book(); und const myMagazine = new Magazine();: Wir erstellen Instanzen der Klassen Book und Magazine.
-
printObject(myBook); und printObject(myMagazine);: Wir rufen die printObject-Funktion mit den Instanzen von Book und Magazine auf. Da sowohl die Buch- als auch die Magazinklasse die Schnittstelle „Druckbar“ implementieren, erfüllen sie die Einschränkung des Typparameters „T erweitert Druckbar“. Innerhalb der Funktion wird die Druckmethode der entsprechenden Klasse aufgerufen, was zur erwarteten Ausgabe führt.
-
// printObject(42);: Wenn wir versuchen, printObject mit einem Typ aufzurufen, der die Printable-Schnittstelle nicht implementiert, wie etwa die Zahl 42, löst TypeScript einen Fehler aus. Dies liegt daran, dass die Typbeschränkung nicht erfüllt ist, da „number“ nicht über eine Druckmethode verfügt, wie sie von der Printable-Schnittstelle gefordert wird.
Zusammenfassend lässt sich sagen, dass das Schlüsselwort „extens“ im Kontext der Funktion printObject(obj: T) verwendet wird, um sicherzustellen, dass der als Argument verwendete Typ T dem von der Printable-Schnittstelle definierten Vertrag entspricht. Dadurch wird sichergestellt, dass nur Typen mit einer Druckmethode mit der printObject-Funktion verwendet werden können, wodurch ein bestimmtes Verhalten und ein bestimmter Vertrag für die Verwendung der Funktion erzwungen werden.
Bedingte Typen
T extends U ? X : Y
-
T ist der Typ, der überprüft wird
-
U ist der Bedingungstyp, gegen den T geprüft wird.
-
X ist der Typ, zu dem der bedingte Typ ausgewertet wird, wenn T U erweitert (zuweisbar ist).
-
Y ist der Typ, zu dem der bedingte Typ ausgewertet wird, wenn T U nicht erweitert
type ExtractNumber<T> = T extends number ? T : never;
type NumberOrNever = ExtractNumber<number>; // number
type StringOrNever = ExtractNumber<string>; // never
Hier akzeptiert der ExtractNumber-Typ einen Typparameter T. Der bedingte Typ prüft, ob T den Zahlentyp erweitert. Wenn dies der Fall ist, wird der Typ in T aufgelöst (das ist der Zahlentyp). Ist dies nicht der Fall, wird der Typ in „nie“ aufgelöst.
Das Schlüsselwort „extends“ mit Union-Typen
Betrachten wir nun den Ausdruck A | B | C erweitert A. Dies mag zunächst kontraintuitiv erscheinen, aber in TypeScript ist diese Bedingung tatsächlich falsch. Hier ist der Grund:
- Wenn Sie in TypeScript Extends mit einem Union-Typ auf der linken Seite verwenden, entspricht dies der Frage: „Ist jeder mögliche Typ in dieser Union dem Typ auf der rechten Seite zuweisbar?“
- Mit anderen Worten, A | B | C erweitert A und fragt: "Kann A A zugewiesen werden, UND kann B A zugewiesen werden, UND kann C A zugewiesen werden?"
- Während A sicherlich A zugeordnet werden kann, sind B und C möglicherweise nicht A zuordenbar (es sei denn, sie sind Untertypen von A), sodass das Gesamtergebnis falsch ist.
type Fruit = "apple" | "banana" | "cherry";
type CitrusFruit = "lemon" | "orange";
type IsCitrus<T> = T extends CitrusFruit ? true : false;
type Test1 = IsCitrus<"lemon">; // true
type Test2 = IsCitrus<"apple">; // false
type Test3 = IsCitrus<Fruit>; // false
In diesem Beispiel ist IsCitrus ist falsch, da nicht alle Früchte in der Fruchtvereinigung Zitrusfrüchte sind.
Best Practices und Tipps
-
Verwenden Sie Erweiterungen für sinnvolle Beziehungen: Verwenden Sie Vererbung nur, wenn eine klare "Ist-ein"-Beziehung zwischen den Typen besteht.
-
Komposition gegenüber Vererbung bevorzugen: In vielen Fällen kann die Komposition (unter Verwendung von Schnittstellen und Typschnittpunkten) flexibler sein als die Klassenvererbung.
-
Seien Sie vorsichtig bei tiefen Vererbungsketten: Eine tiefe Vererbung kann das Verständnis und die Wartung von Code erschweren.
-
Bedingte Typen für flexible APIs nutzen: Verwenden Sie bedingte Typen mit Erweiterungen, um APIs zu erstellen, die sich basierend auf Eingabetypen anpassen.
-
Verwenden Sie Erweiterungen in Generika, um wiederverwendbare, typsichere Funktionen zu erstellen: Dadurch können Sie Funktionen schreiben, die mit einer Vielzahl von Typen funktionieren und gleichzeitig die Typsicherheit beibehalten
Das obige ist der detaillierte Inhalt vonTypeScript beherrschen: Die Leistungsfähigkeit von Extends verstehen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!