TypeScript - 通用類別
你好,未來的編程超新星!今天,我們要深入探索 TypeScript 通用類別的精彩世界。如果你是編程新手,別擔心;我會一步一步地引導你,就像這些年來我對無數學生所做的一樣。所以,拿起你喜歡的飲料,舒適地坐好,讓我們一起踏上這個冒險旅程!
通用類別
什麼是通用類別?
想像你在一個冰淇淋店,但不是在選擇口味,而是在挑選數據類型。這就是通用類別的精髓!它們讓我們能夠創建靈活、可重用的組件,這些組件可以與不同的數據類型一起工作,同時不犧牲類型安全。
讓我們從一個簡單的例子開始:
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