Heim > Web-Frontend > js-Tutorial > Die wichtigsten fortgeschrittenen TypeScript-Konzepte, die jeder Entwickler kennen sollte

Die wichtigsten fortgeschrittenen TypeScript-Konzepte, die jeder Entwickler kennen sollte

DDD
Freigeben: 2024-11-26 07:18:10
Original
924 Leute haben es durchsucht

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.

Top Advanced typescript concepts that Every Developer Should Know

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;
}
Nach dem Login kopieren
Nach dem Login kopieren

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'
Nach dem Login kopieren
Nach dem Login kopieren

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" }

Nach dem Login kopieren
Nach dem Login kopieren

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" 
Nach dem Login kopieren
Nach dem Login kopieren

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"'.

Nach dem Login kopieren
Nach dem Login kopieren

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

Nach dem Login kopieren
Nach dem Login kopieren

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"'.

Nach dem Login kopieren

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);

Nach dem Login kopieren

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;
Nach dem Login kopieren

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'
Nach dem Login kopieren

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;
}
Nach dem Login kopieren
Nach dem Login kopieren

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'
Nach dem Login kopieren
Nach dem Login kopieren

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" }

Nach dem Login kopieren
Nach dem Login kopieren

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" 
Nach dem Login kopieren
Nach dem Login kopieren

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"'.

Nach dem Login kopieren
Nach dem Login kopieren

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

Nach dem Login kopieren
Nach dem Login kopieren

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!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage