Heim >Web-Frontend >js-Tutorial >Die wichtigsten fortgeschrittenen TypeScript-Konzepte, die jeder Entwickler kennen sollte
TypeScript ist eine moderne Programmiersprache, die aufgrund ihrer zusätzlichen Typsicherheit häufig JavaScript vorgezogen wird. In diesem Artikel teile ich die 10 besten TypeScript-Konzepte, mit denen Sie Ihre TypeScript-Programmierkenntnisse verbessern können. Sind Sie bereit? Los geht's.
1.Generika: Mithilfe von Generika können wir wiederverwendbare Typen erstellen, die beim Umgang mit den Daten von heute und morgen hilfreich sein werden.
Beispiel für Generika:
Wir möchten möglicherweise eine Funktion in Typescript, die ein Argument als einen Typ annimmt, und wir möchten möglicherweise denselben Typ zurückgeben.
function func<T>(args:T):T{ return args; }
2.Generika mit Typbeschränkungen: Beschränken wir nun den Typ T, indem wir ihn so definieren, dass er nur Zeichenfolgen und Ganzzahlen akzeptiert:
function func<T extends string | number>(value: T): T { return value; } const stringValue = func("Hello"); // Works, T is string const numberValue = func(42); // Works, T is number // const booleanValue = func(true); // Error: Type 'boolean' is not assignable to type 'string | number'
3.Generische Schnittstellen:
Schnittstellengenerika sind nützlich, wenn Sie Verträge (Formen) für Objekte, Klassen oder Funktionen definieren möchten, die mit einer Vielzahl von Typen arbeiten. Sie ermöglichen es Ihnen, einen Entwurf zu definieren, der sich an verschiedene Datentypen anpassen lässt und gleichzeitig die Struktur konsistent hält.
// Generic interface with type parameters T and U interface Repository<T, U> { items: T[]; // Array of items of type T add(item: T): void; // Function to add an item of type T getById(id: U): T | undefined; // Function to get an item by ID of type U } // Implementing the Repository interface for a User entity interface User { id: number; name: string; } class UserRepository implements Repository<User, number> { items: User[] = []; add(item: User): void { this.items.push(item); } getById(idOrName: number | string): User | undefined { if (typeof idOrName === 'string') { // Search by name if idOrName is a string console.log('Searching by name:', idOrName); return this.items.find(user => user.name === idOrName); } else if (typeof idOrName === 'number') { // Search by id if idOrName is a number console.log('Searching by id:', idOrName); return this.items.find(user => user.id === idOrName); } return undefined; // Return undefined if no match found } } // Usage const userRepo = new UserRepository(); userRepo.add({ id: 1, name: "Alice" }); userRepo.add({ id: 2, name: "Bob" }); const user1 = userRepo.getById(1); const user2 = userRepo.getById("Bob"); console.log(user1); // Output: { id: 1, name: "Alice" } console.log(user2); // Output: { id: 2, name: "Bob" }
4.Generische Klassen:: Verwenden Sie dies, wenn Sie möchten, dass alle Eigenschaften in Ihrer Klasse dem durch den generischen Parameter angegebenen Typ entsprechen. Dies ermöglicht Flexibilität und stellt gleichzeitig sicher, dass jede Eigenschaft der Klasse mit dem an die Klasse übergebenen Typ übereinstimmt.
interface User { id: number; name: string; age: number; } class UserDetails<T extends User> { id: T['id']; name: T['name']; age: T['age']; constructor(user: T) { this.id = user.id; this.name = user.name; this.age = user.age; } // Method to get user details getUserDetails(): string { return `User: ${this.name}, ID: ${this.id}, Age: ${this.age}`; } // Method to update user name updateName(newName: string): void { this.name = newName; } // Method to update user age updateAge(newAge: number): void { this.age = newAge; } } // Using the UserDetails class with a User type const user: User = { id: 1, name: "Alice", age: 30 }; const userDetails = new UserDetails(user); console.log(userDetails.getUserDetails()); // Output: "User: Alice, ID: 1, Age: 30" // Updating user details userDetails.updateName("Bob"); userDetails.updateAge(35); console.log(userDetails.getUserDetails()); // Output: "User: Bob, ID: 1, Age: 35" console.log(new UserDetails("30")); // Error: "This will throw error"
5.Typparameter auf übergebene Typen beschränken: Manchmal möchten wir, dass ein Parametertyp von einigen anderen übergebenen Parametern abhängt. Klingt verwirrend, sehen wir uns das Beispiel unten an.
function getProperty<Type>(obj: Type, key: keyof Type) { return obj[key]; } let x = { a: 1, b: 2, c: 3 }; getProperty(x, "a"); // Valid getProperty(x, "d"); // Error: Argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'.
6.Bedingte Typen: Oft möchten wir, dass unsere Typen entweder dem einen oder dem anderen Typ angehören. In solchen Situationen verwenden wir bedingte Typen.
Ein einfaches Beispiel wäre:
function func(param:number|boolean){ return param; } console.log(func(2)) //Output: 2 will be printed console.log(func("True")) //Error: boolean cannot be passed as argument
Ein etwas komplexes Beispiel:
type HasProperty<T, K extends keyof T> = K extends "age" ? "Has Age" : "Has Name"; interface User { name: string; age: number; } let test1: HasProperty<User, "age">; // "Has Age" let test2: HasProperty<User, "name">; // "Has Name" let test3: HasProperty<User, "email">; // Error: Type '"email"' is not assignable to parameter of type '"age" | "name"'.
6.Überschneidungstypen:Diese Typen sind nützlich, wenn wir mehrere Typen zu einem kombinieren möchten, sodass ein bestimmter Typ Eigenschaften und Verhaltensweisen von verschiedenen anderen Typen erben kann.
Sehen wir uns hierfür ein interessantes Beispiel an:
// Defining the types for each area of well-being interface MentalWellness { mindfulnessPractice: boolean; stressLevel: number; // Scale of 1 to 10 } interface PhysicalWellness { exerciseFrequency: string; // e.g., "daily", "weekly" sleepDuration: number; // in hours } interface Productivity { tasksCompleted: number; focusLevel: number; // Scale of 1 to 10 } // Combining all three areas into a single type using intersection types type HealthyBody = MentalWellness & PhysicalWellness & Productivity; // Example of a person with a balanced healthy body const person: HealthyBody = { mindfulnessPractice: true, stressLevel: 4, exerciseFrequency: "daily", sleepDuration: 7, tasksCompleted: 15, focusLevel: 8 }; // Displaying the information console.log(person);
7.infer-Schlüsselwort: Das infer-Schlüsselwort ist nützlich, wenn wir einen bestimmten Typ bedingt bestimmen möchten, und wenn die Bedingung erfüllt ist, können wir Untertypen aus diesem Typ extrahieren.
Dies ist die allgemeine Syntax:
type ConditionalType<T> = T extends SomeType ? InferredType : OtherType;
Beispiel hierfür:
type ReturnTypeOfPromise<T> = T extends Promise<infer U> ? U : number; type Result = ReturnTypeOfPromise<Promise<string>>; // Result is 'string' type ErrorResult = ReturnTypeOfPromise<number>; // ErrorResult is 'never' const result: Result = "Hello"; console.log(typeof result); // Output: 'string'
8.Typvarianz: Dieses Konzept beschreibt, wie Subtyp und Supertyp miteinander in Beziehung stehen.
Es gibt zwei Arten:
Kovarianz: Ein Subtyp kann dort verwendet werden, wo ein Supertyp erwartet wird.
Sehen wir uns hierfür ein Beispiel an:
function func<T>(args:T):T{ return args; }
Im obigen Beispiel hat das Auto Eigenschaften von der Fahrzeugklasse geerbt, daher ist es absolut gültig, es einem Untertyp zuzuweisen, bei dem der Supertyp erwartet wird, da der Untertyp alle Eigenschaften haben würde, die ein Supertyp hat.
Kontravarianz: Dies ist das Gegenteil von Kovarianz. Wir verwenden Supertypen an Stellen, an denen subType erwartet wird.
function func<T extends string | number>(value: T): T { return value; } const stringValue = func("Hello"); // Works, T is string const numberValue = func(42); // Works, T is number // const booleanValue = func(true); // Error: Type 'boolean' is not assignable to type 'string | number'
Bei der Verwendung von Kontravarianz müssen wir darauf achten, nicht auf Eigenschaften oder Methoden zuzugreifen, die für den Subtyp spezifisch sind, da dies zu einem Fehler führen kann.
9. Überlegungen: Bei diesem Konzept geht es darum, den Typ einer Variablen zur Laufzeit zu bestimmen. Während sich TypeScript hauptsächlich auf die Typprüfung zur Kompilierungszeit konzentriert, können wir TypeScript-Operatoren dennoch nutzen, um Typen während der Laufzeit zu überprüfen.
Typeof-Operator: Wir können den Typeof-Operator verwenden, um den Typ der Variablen zur Laufzeit zu ermitteln
// Generic interface with type parameters T and U interface Repository<T, U> { items: T[]; // Array of items of type T add(item: T): void; // Function to add an item of type T getById(id: U): T | undefined; // Function to get an item by ID of type U } // Implementing the Repository interface for a User entity interface User { id: number; name: string; } class UserRepository implements Repository<User, number> { items: User[] = []; add(item: User): void { this.items.push(item); } getById(idOrName: number | string): User | undefined { if (typeof idOrName === 'string') { // Search by name if idOrName is a string console.log('Searching by name:', idOrName); return this.items.find(user => user.name === idOrName); } else if (typeof idOrName === 'number') { // Search by id if idOrName is a number console.log('Searching by id:', idOrName); return this.items.find(user => user.id === idOrName); } return undefined; // Return undefined if no match found } } // Usage const userRepo = new UserRepository(); userRepo.add({ id: 1, name: "Alice" }); userRepo.add({ id: 2, name: "Bob" }); const user1 = userRepo.getById(1); const user2 = userRepo.getById("Bob"); console.log(user1); // Output: { id: 1, name: "Alice" } console.log(user2); // Output: { id: 2, name: "Bob" }
Instanceof-Operator: Der Instanzof-Operator kann verwendet werden, um zu überprüfen, ob ein Objekt eine Instanz einer Klasse oder eines bestimmten Typs ist.
interface User { id: number; name: string; age: number; } class UserDetails<T extends User> { id: T['id']; name: T['name']; age: T['age']; constructor(user: T) { this.id = user.id; this.name = user.name; this.age = user.age; } // Method to get user details getUserDetails(): string { return `User: ${this.name}, ID: ${this.id}, Age: ${this.age}`; } // Method to update user name updateName(newName: string): void { this.name = newName; } // Method to update user age updateAge(newAge: number): void { this.age = newAge; } } // Using the UserDetails class with a User type const user: User = { id: 1, name: "Alice", age: 30 }; const userDetails = new UserDetails(user); console.log(userDetails.getUserDetails()); // Output: "User: Alice, ID: 1, Age: 30" // Updating user details userDetails.updateName("Bob"); userDetails.updateAge(35); console.log(userDetails.getUserDetails()); // Output: "User: Bob, ID: 1, Age: 35" console.log(new UserDetails("30")); // Error: "This will throw error"
Wir können Bibliotheken von Drittanbietern verwenden, um Typen zur Laufzeit zu bestimmen.
10.Abhängigkeitsinjektion:Abhängigkeitsinjektion ist ein Muster, das es Ihnen ermöglicht, Code in Ihre Komponente einzubringen, ohne ihn dort tatsächlich zu erstellen oder zu verwalten. Auch wenn es den Anschein hat, als würde man eine Bibliothek verwenden, ist es doch anders, weil man sie nicht über ein CDN oder eine API installieren oder importieren muss.
Auf den ersten Blick scheint es auch ähnlich zu sein wie die Verwendung von Funktionen zur Wiederverwendbarkeit, da beide die Wiederverwendung von Code ermöglichen. Wenn wir Funktionen jedoch direkt in unseren Komponenten verwenden, kann es zu einer engen Kopplung zwischen ihnen kommen. Das bedeutet, dass jede Änderung der Funktion oder ihrer Logik Auswirkungen auf jeden Ort haben kann, an dem sie verwendet wird.
Dependency Injection löst dieses Problem, indem es die Erstellung von Abhängigkeiten von den Komponenten entkoppelt, die sie verwenden, wodurch der Code wartbarer und testbarer wird.
Beispiel ohne Abhängigkeitsinjektion
function getProperty<Type>(obj: Type, key: keyof Type) { return obj[key]; } let x = { a: 1, b: 2, c: 3 }; getProperty(x, "a"); // Valid getProperty(x, "d"); // Error: Argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'.
Beispiel mit Abhängigkeitsinjektion
function func(param:number|boolean){ return param; } console.log(func(2)) //Output: 2 will be printed console.log(func("True")) //Error: boolean cannot be passed as argument
Wenn Sie in einem eng gekoppelten Szenario heute ein stressLevel-Attribut in der MentalWellness-Klasse haben und beschließen, es morgen in etwas anderes zu ändern, müssten Sie alle Orte aktualisieren, an denen es verwendet wurde. Dies kann zu vielen Refactoring- und Wartungsherausforderungen führen.
Mit der Abhängigkeitsinjektion und der Verwendung von Schnittstellen können Sie dieses Problem jedoch vermeiden. Indem die Abhängigkeiten (z. B. der MentalWellness-Dienst) durch den Konstruktor geleitet werden, werden die spezifischen Implementierungsdetails (z. B. das stressLevel-Attribut) hinter der Schnittstelle abstrahiert. Dies bedeutet, dass Änderungen am Attribut oder an der Klasse keine Änderungen in den abhängigen Klassen erfordern, solange die Schnittstelle gleich bleibt. Dieser Ansatz stellt sicher, dass der Code lose gekoppelt, wartbarer und einfacher zu testen ist, da Sie zur Laufzeit das einfügen, was benötigt wird, ohne Komponenten eng zu koppeln.
Das obige ist der detaillierte Inhalt vonDie wichtigsten fortgeschrittenen TypeScript-Konzepte, die jeder Entwickler kennen sollte. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!