阿川私房教材:學程式,拿 offer!

63 個專案實戰,直接上手!
無需補習,按步驟打造你的面試作品。

立即解鎖你的轉職秘笈

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

我們來貓吧

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.類型差異:這個概念討論了子類型和父類型如何相互關聯。

這些有兩種類型:

協方差:可以在需要超類型的地方使用子類型。

讓我們來看一個例子:

class Vehicle {
  start() {
    console.log("Vehicle is running");
  }
}

class Car extends Vehicle {
  honk() {
    console.log("this vehicle honks");
  }
}

function vehiclefunc(vehicle: Vehicle) {
  vehicle.start(); 
}

function carfunc(car: Car) {
  car.start();        // Works because Car extends Vehicle(inheritance)
  car.honk();        // Works because 'Car' has 'honk'
}

let car: Car = new Car();
vehiclefunc(car); // Allowed due to covariance

在上面的範例中,Car 繼承了 Vehicle 類別的屬性,因此將其指派給需要超類型的子類型是絕對有效的,因為子類型將具有超類型所具有的所有屬性。

逆變:這與協變相反。

class Vehicle {
  startEngine() {
    console.log("Vehicle engine starts");
  }
}

class Car extends Vehicle {
  honk() {
    console.log("Car honks");
  }
}

function processVehicle(vehicle: Vehicle) {
  vehicle.startEngine(); // This works
  // vehicle.honk(); // Error: 'honk' does not exist on type 'Vehicle'
}

function processCar(car: Car) {
  car.startEngine(); // Works because Car extends Vehicle
  car.honk();        // Works because 'Car' has 'honk'
}

let car: Car = new Car();
processVehicle(car); // This works because of contravariance (Car can be used as Vehicle)
processCar(car);     // This works as well because car is of type Car

// Contravariance failure if you expect specific subtype behavior in the method

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

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

typeof運算子:我們可以利用typeof運算子在執行階段尋找變數的類型

const num = 23;
console.log(typeof num); // "number"

const flag = true;
console.log(typeof flag); // "boolean"

instanceof運算子: instanceof 運算子可用於檢查物件是否為類別或特定類型的實例。

class Vehicle {
  model: string;
  constructor(model: string) {
    this.model = model;
  }
}

const benz = new Vehicle("Mercedes-Benz");
console.log(benz instanceof Vehicle); // true

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

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

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

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

沒有依賴注入的範例

// Health-related service classes without interfaces
class MentalWellness {
  getMentalWellnessAdvice(): string {
    return "Take time to meditate and relax your mind.";
  }
}

class PhysicalWellness {
  getPhysicalWellnessAdvice(): string {
    return "Make sure to exercise daily for at least 30 minutes.";
  }
}

// HealthAdvice class directly creating instances of the services
class HealthAdvice {
  private mentalWellnessService: MentalWellness;
  private physicalWellnessService: PhysicalWellness;

  // Directly creating instances inside the class constructor
  constructor() {
    this.mentalWellnessService = new MentalWellness();
    this.physicalWellnessService = new PhysicalWellness();
  }

  // Method to get both mental and physical wellness advice
  getHealthAdvice(): string {
    return `${this.mentalWellnessService.getMentalWellnessAdvice()} Also, ${this.physicalWellnessService.getPhysicalWellnessAdvice()}`;
  }
}

// Creating an instance of HealthAdvice, which itself creates instances of the services
const healthAdvice = new HealthAdvice();

console.log(healthAdvice.getHealthAdvice());
// Output: "Take time to meditate and relax your mind. Also, Make sure to exercise daily for at least 30 minutes."

依賴注入範例

// Health-related service interfaces with "I" prefix
interface IMentalWellnessService {
  getMentalWellnessAdvice(): string;
}

interface IPhysicalWellnessService {
  getPhysicalWellnessAdvice(): string;
}

// Implementations of the services
class MentalWellness implements IMentalWellnessService {
  getMentalWellnessAdvice(): string {
    return "Take time to meditate and relax your mind.";
  }
}

class PhysicalWellness implements IPhysicalWellnessService {
  getPhysicalWellnessAdvice(): string {
    return "Make sure to exercise daily for at least 30 minutes.";
  }
}

// HealthAdvice class that depends on services via interfaces
class HealthAdvice {
  private mentalWellnessService: IMentalWellnessService;
  private physicalWellnessService: IPhysicalWellnessService;

  // Dependency injection via constructor
  constructor(
    mentalWellnessService: IMentalWellnessService,
    physicalWellnessService: IPhysicalWellnessService
  ) {
    this.mentalWellnessService = mentalWellnessService;
    this.physicalWellnessService = physicalWellnessService;
  }

  // Method to get both mental and physical wellness advice
  getHealthAdvice(): string {
    return `${this.mentalWellnessService.getMentalWellnessAdvice()} Also, ${this.physicalWellnessService.getPhysicalWellnessAdvice()}`;
  }
}

// Dependency injection
const mentalWellness: IMentalWellnessService = new MentalWellness();
const physicalWellness: IPhysicalWellnessService = new PhysicalWellness();

// Injecting services into the HealthAdvice class
const healthAdvice = new HealthAdvice(mentalWellness, physicalWellness);

console.log(healthAdvice.getHealthAdvice());
// Output: "Take time to meditate and relax your mind. Also, Make sure to exercise daily for at least 30 minutes."

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

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


原文出處:https://dev.to/niharikaa/top-10-advanced-typescript-concepts-that-every-developer-should-know-4kg4


共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。

阿川私房教材:學程式,拿 offer!

63 個專案實戰,直接上手!
無需補習,按步驟打造你的面試作品。

立即解鎖你的轉職秘笈