首頁  >  文章  >  web前端  >  每個開發人員都應該了解的高級打字稿概念

每個開發人員都應該了解的高級打字稿概念

DDD
DDD原創
2024-11-26 07:18:10863瀏覽

TypeScript 是一種現代程式語言,由於其附加的型別安全性,通常比 JavaScript 更受青睞。在本文中,我將分享 10 個最重要的 TypeScript 概念,這將有助於提升您的 TypeScript 程式設計技能。準備好了嗎?開始吧。

Top Advanced typescript concepts that Every Developer Should Know

1.泛型:使用泛型我們可以建立可重複使用的類型,這將有助於處理今天和明天的資料。
泛型範例:
我們可能想要 Typescript 中的一個函數將參數作為某種類型,並且我們可能想要傳回相同的類型。

function func<T>(args:T):T{
    return args;
}

2.具有型別約束的泛型:現在讓我們透過定義它只接受字串和整數來限制型別 T:

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.通用介面:
當您想要為使用各種類型的物件、類別或函數定義契約(形狀)時,介面泛型非常有用。它們允許您定義一個藍圖,該藍圖可以適應不同的資料類型,同時保持結構一致。

// 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.泛型類別::當您希望類別中的所有屬性都遵循泛型參數指定的類型時,請使用此選項。這允許靈活性,同時確保類別的每個屬性都與傳遞給類別的類型相符。

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.將型別參數約束為傳遞的型別:有時,我們希望參數型別依賴其他一些傳遞的參數。聽起來很混亂,讓我們看下面的範例。

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.條件類型:通常,我們希望我們的類型是一種類型或另一種類型。在這種情況下,我們使用條件類型。
一個簡單的例子是:

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

有點複雜的例子:

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.交集類型:當我們想要將多種類型合併為一個類型時,這些類型非常有用,允許特定類型繼承各種其他類型的屬性和行為。
讓我們來看一個有趣的例子:

// 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 關鍵字:當我們想要有條件地確定特定類型時,infer 關鍵字很有用,並且當滿足條件時,它允許我們從該類型中提取子類型。
這是一般語法:

type ConditionalType<T> = T extends SomeType ? InferredType : OtherType;

範例:

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.Type Variance:這個概念討論了子類型和父類型如何相互關聯。
它們有兩種類型:
協方差:可以在需要父類型的地方使用子類型。
讓我們來看一個例子:

function func<T>(args:T):T{
    return args;
}

在上面的範例中,Car 繼承了 Vehicle 類別的屬性,因此將其指派給需要超類型的子類型是絕對有效的,因為子類型將具有超類型所具有的所有屬性。
逆變:這與協變相反。我們在子類型應該出現的地方使用超類型。

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'

使用逆變時,我們需要小心不要存取特定於子類型的屬性或方法,因為這可能會導致錯誤。

9。反思: 這個概念涉及在運行時確定變數的類型。雖然 TypeScript 主要專注於編譯時的類型檢查,但我們仍然可以利用 TypeScript 運算子在執行時檢查類型。
typeof 運算子:我們可以利用 typeof 運算子在執行時尋找變數的型別

// 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 運算子:instanceof 運算子可用於檢查物件是否為類別或特定類型的實例。

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" 

我們可以使用第三方函式庫在執行時確定類型。

10.依賴注入:依賴注入是一種模式,允許您將程式碼引入元件中,而無需在元件中實際建立或管理它。雖然它看起來像是使用庫,但它有所不同,因為您不需要透過 CDN 或 API 安裝或匯入它。

乍一看,它似乎也類似於使用函數來實現可重複使用性,因為兩者都允許程式碼重複使用。然而,如果我們直接在組件中使用函數,可能會導致它們之間的緊密耦合。這意味著函數或其邏輯的任何變化都可能影響它使用的每個地方。

依賴注入透過將依賴項的建立與使用它們的元件解耦來解決這個問題,使程式碼更易於維護和測試。

沒有依賴注入的範例

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

依賴注入範例

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

在緊密耦合的場景中,如果您今天 MentalWellness 類別中有一個stressLevel 屬性,並決定明天將其更改為其他屬性,則需要更新所有使用它的地方。這可能會導致許多重構和維護挑戰。

但是,透過依賴注入和介面的使用,你可以避免這個問題。透過建構函式傳遞依賴項(例如 MentalWellness 服務),具體的實作細節(例如stressLevel 屬性)被抽像到介面後面。這意味著只要介面保持不變,對屬性或類別的變更不需要修改依賴類別。這種方法可確保程式碼鬆散耦合、更易於維護且更易於測試,因為您可以在運行時注入所需的內容,而無需緊密耦合元件。

以上是每個開發人員都應該了解的高級打字稿概念的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn