TypeScript 泛型基礎:函式、介面、類別
泛型(Generics)是 TypeScript 最強大的特性之一。本篇將帶你從零開始掌握泛型的核心概念。
一、 為什麼需要泛型?
1.1 問題場景
typescript
// 這個函式只能處理 number
function identityNumber(value: number): number {
return value;
}
// 要處理 string 得再寫一個
function identityString(value: string): string {
return value;
}
// 每種類型都寫一個?太麻煩了!1.2 用 any?不安全
typescript
function identity(value: any): any {
return value;
}
const result = identity("hello");
// result 是 any,失去了類型資訊
result.toUpperCase(); // 沒有自動完成提示1.3 泛型解決方案
typescript
function identity<T>(value: T): T {
return value;
}
const str = identity("hello"); // str: string
const num = identity(42); // num: number
str.toUpperCase(); // 有自動完成!二、 泛型函式
2.1 基本語法
typescript
// <T> 是類型參數
function identity<T>(value: T): T {
return value;
}
// 明確指定類型
const a = identity<string>("hello");
// 類型推論(推薦)
const b = identity("hello"); // 自動推論為 string2.2 多個類型參數
typescript
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const result = pair("hello", 42); // [string, number]2.3 箭頭函式
typescript
// 一般寫法
const identity = <T>(value: T): T => {
return value;
};
// 在 TSX 中需要加逗號避免與 JSX 混淆
const identity2 = <T>(value: T): T => value;三、 泛型介面
3.1 基本用法
typescript
interface Container<T> {
value: T;
getValue(): T;
}
const stringContainer: Container<string> = {
value: "hello",
getValue() {
return this.value;
},
};3.2 API 回應
typescript
interface ApiResponse<T> {
success: boolean;
data: T;
error?: string;
}
interface User {
id: number;
name: string;
}
async function fetchUser(): Promise<ApiResponse<User>> {
// ...
}
const response = await fetchUser();
if (response.success) {
console.log(response.data.name); // 有類型提示!
}3.3 泛型函式類型
typescript
interface Transformer<T, U> {
(input: T): U;
}
const stringToNumber: Transformer<string, number> = (str) => {
return parseInt(str, 10);
};四、 泛型類別
4.1 基本用法
typescript
class Box<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
setValue(value: T): void {
this.value = value;
}
}
const stringBox = new Box("hello");
const numberBox = new Box(42);4.2 泛型堆疊
typescript
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
isEmpty(): boolean {
return this.items.length === 0;
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 2五、 泛型 type
5.1 基本用法
typescript
type Result<T> =
| {
success: true;
data: T;
}
| {
success: false;
error: string;
};
function handleResult<T>(result: Result<T>) {
if (result.success) {
console.log(result.data);
} else {
console.error(result.error);
}
}5.2 泛型工具類型
typescript
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
type Maybe<T> = T | null | undefined;
let name: Nullable<string> = null;
name = "John";六、 實用範例
6.1 陣列操作
typescript
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
function last<T>(arr: T[]): T | undefined {
return arr[arr.length - 1];
}
function filter<T>(arr: T[], predicate: (item: T) => boolean): T[] {
return arr.filter(predicate);
}
const numbers = [1, 2, 3, 4, 5];
const firstNum = first(numbers); // number | undefined
const evens = filter(numbers, (n) => n % 2 === 0); // number[]6.2 物件操作
typescript
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "John", age: 30 };
const name = getProperty(user, "name"); // string
const age = getProperty(user, "age"); // number
// getProperty(user, 'email') // 錯誤!'email' 不是有效的 key6.3 Promise 包裝
typescript
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
return response.json();
}
interface User {
id: number;
name: string;
}
const user = await fetchData<User>("/api/user");
console.log(user.name); // 有類型提示七、 常見錯誤
7.1 忘記使用類型參數
typescript
// 錯誤:T 沒有被使用
function bad<T>(value: string): string {
return value;
}
// 正確
function good<T>(value: T): T {
return value;
}7.2 過度使用泛型
typescript
// 不需要泛型
function unnecessary<T extends string>(str: T): string {
return str.toUpperCase();
}
// 直接用 string
function better(str: string): string {
return str.toUpperCase();
}總結
| 語法 | 說明 | 範例 |
|---|---|---|
<T> | 類型參數 | function fn<T>() |
| 多參數 | 多個類型 | <T, U, V> |
| 推論 | 自動判斷 | fn('hello') |
| 明確 | 手動指定 | fn<string>('hello') |
> **使用時機**:
- 函式輸入輸出類型相關
- 容器類型(陣列、集合)
- 需要保留類型資訊
進階挑戰
- 實作一個泛型
swap函式,交換元組的兩個元素 - 實作一個泛型
Queue<T>類別 - 為
localStorage封裝一個類型安全的包裝函式
typescript
// 練習 3 提示
function getItem<T>(key: string): T | null {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : null;
}