TypeScript - 通用類別

你好,未來的編程超新星!今天,我們要深入探索 TypeScript 通用類別的精彩世界。如果你是編程新手,別擔心;我會一步一步地引導你,就像這些年來我對無數學生所做的一樣。所以,拿起你喜歡的飲料,舒適地坐好,讓我們一起踏上這個冒險旅程!

TypeScript - Generic Classes

通用類別

什麼是通用類別?

想像你在一個冰淇淋店,但不是在選擇口味,而是在挑選數據類型。這就是通用類別的精髓!它們讓我們能夠創建靈活、可重用的組件,這些組件可以與不同的數據類型一起工作,同時不犧牲類型安全。

讓我們從一個簡單的例子開始:

class Box<T> {
private content: T;

constructor(value: T) {
this.content = value;
}

getValue(): T {
return this.content;
}
}

在這個例子中,Box 是一個通用類別。<T> 像是一個占位符,我們稍後會指定類型。這就像告訴冰淇淋店:"我訂單時再決定口味!"

讓我們分解一下:

  • class Box<T>:這聲明了一個名為 Box 的通用類別,帶有一個類型參數 T
  • private content: T:我們說 content 將是 T 類型,無論 T 是什麼。
  • constructor(value: T):構造函數接受一個 T 類型的值。
  • getValue(): T:這個方法返回一個 T 類型的值。

現在,讓我們看看如何使用這個類別:

let numberBox = new Box<number>(42);
console.log(numberBox.getValue()); // 輸出:42

let stringBox = new Box<string>("Hello, TypeScript!");
console.log(stringBox.getValue()); // 輸出:Hello, TypeScript!

這不是很酷嗎?我們使用了同樣的 Box 類別來存儲數字和字符串。這就像有一個神奇的盒子,可以裝進你放入的任何東西,但仍然記得它所持有的確切類型!

多個類型參數

有時,一個類型參數是不夠的。讓我們創建一個更複雜的例子,帶有多個類型參數:

class Pair<T, U> {
private first: T;
private second: U;

constructor(first: T, second: U) {
this.first = first;
this.second = second;
}

getFirst(): T {
return this.first;
}

getSecond(): U {
return this.second;
}
}

這個 Pair 類別可以持有兩個可能不同類型的值。這就像有一個雙冰淇淋蛋筒,每個球可以是不同的口味!

讓我們使用我們的 Pair 類別:

let pair = new Pair<string, number>("Age", 30);
console.log(pair.getFirst());  // 輸出:Age
console.log(pair.getSecond()); // 輸出:30

通用約束

有時,我們想限制可以用在我們通用類別中的類型。我們可以使用約束來做到這點。這就像說:"你可以選擇任何冰淇淋口味,只要它不過於辣!"

interface Lengthwise {
length: number;
}

class LengthChecker<T extends Lengthwise> {
checkLength(obj: T): string {
return `The length is: ${obj.length}`;
}
}

在這個例子中,T extends Lengthwise 意味著 T 必須是一個有 length 屬性的類型。讓我們使用它:

let stringChecker = new LengthChecker<string>();
console.log(stringChecker.checkLength("Hello")); // 輸出:The length is: 5

let arrayChecker = new LengthChecker<number[]>();
console.log(arrayChecker.checkLength([1, 2, 3])); // 輸出:The length is: 3

// 這會導致錯誤:
// let numberChecker = new LengthChecker<number>();
// Type 'number' does not satisfy the constraint 'Lengthwise'.

在通用類別中實現通用接口

現在,讓我們將我們的技能提升到下一個水平,通過在通用類別中實現通用接口。這就像為不同類型的冰淇淋(類別)創建一個配方(接口)!

首先,讓我們定義一個通用接口:

interface Repository<T> {
getById(id: number): T;
save(item: T): void;
}

這個 Repository 接口定義了一個合同,用於處理數據存儲和檢索的類別。現在,讓我們用一個通用類別實現這個接口:

class GenericRepository<T> implements Repository<T> {
private items: T[] = [];

getById(id: number): T {
return this.items[id];
}

save(item: T): void {
this.items.push(item);
}
}

我們的 GenericRepository 類別實現了 Repository 接口。它可以與任何類型 T 一起工作。讓我們使用它:

interface User {
name: string;
age: number;
}

let userRepo = new GenericRepository<User>();

userRepo.save({ name: "Alice", age: 30 });
userRepo.save({ name: "Bob", age: 25 });

console.log(userRepo.getById(0)); // 輸出:{ name: "Alice", age: 30 }
console.log(userRepo.getById(1)); // 輸出:{ name: "Bob", age: 25 }

在這個例子中,我們創建了一個 User 對象的倉庫。但我們通用實現的美妙之處在於,我們也可以輕鬆地為任何其他類型創建倉庫!

方法表

這裡有一個方便的表格,總結了我們所涵蓋的方法:

方法 描述 示例
constructor(value: T) 創建一個通用類別的新實例 new Box<number>(42)
getValue(): T 從通用類別返回存儲的值 numberBox.getValue()
getFirst(): T 返回對中的一個值 pair.getFirst()
getSecond(): U 返回對中的第二個值 pair.getSecond()
checkLength(obj: T): string 檢查對象(帶約束)的長度 stringChecker.checkLength("Hello")
getById(id: number): T 根據 ID 從倉庫中检索項目 userRepo.getById(0)
save(item: T): void 將項目保存到倉庫 userRepo.save({ name: "Alice", age: 30 })

好了,各位!我們已經一起穿越了 TypeScript 通用類別的土地,從基本的盒子到複雜的倉庫。記住,熟能生巧,所以不要害怕嘗試這些概念。誰知道呢?你可能會創造出編程界下一個大熱門!下次見,快樂編程!

Credits: Image by storyset