接續 TypeScript 基礎篇(同目錄
basics.md);條件型別、工具型別等更進階用法見進階型別篇。
目錄
什麼是泛型?
泛型(Generics) 讓你寫出「型別可重用」的程式:函式或型別先不指定具體型別,留一個型別參數,呼叫時才代入——既保有彈性,又保留型別安全。
為什麼需要泛型?
考慮一個「回傳傳入值」的函式:
// ❌ 用 any:失去型別,回傳變 any
function identity(x: any): any { return x; }
const a = identity("hi"); // a 是 any(沒型別保護)
// ❌ 寫死型別:不能重用
function identityStr(x: string): string { return x; }
// ✅ 泛型:彈性 + 型別安全
function identity<T>(x: T): T { return x; }
const b = identity("hi"); // b 推論為 string
const c = identity(123); // c 推論為 number
<T> 是型別參數(慣例用 T、U、K、V),它在呼叫時由實際引數推論或明確指定。
核心價值:用
any是放棄型別;用泛型是把型別當參數傳遞,兩者天差地別。
泛型函式
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const n = first([1, 2, 3]); // number | undefined(自動推論)
const s = first(["a", "b"]); // string | undefined
// 也可明確指定型別參數
const x = first<boolean>([true]); // 明確指定 T = boolean
- 型別參數通常能自動推論,不必每次手動寫
<...> - 只有推不出或想更精確時才明確指定
泛型介面與型別別名
型別本身也能泛型化,常用於容器、包裝結構:
// 泛型介面
interface Box<T> {
value: T;
}
const numBox: Box<number> = { value: 42 };
// 泛型型別別名
type Pair<K, V> = { key: K; value: V };
const p: Pair<string, number> = { key: "age", value: 30 };
// 泛型函式型別
type Mapper<T, U> = (item: T) => U;
const toLength: Mapper<string, number> = (s) => s.length;
泛型類別
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]; }
}
const s = new Stack<number>();
s.push(1);
const top = s.pop(); // number | undefined
泛型類別讓同一份資料結構(Stack、Queue、LinkedList…)能安全地裝任何型別。
泛型約束(Constraints)
預設型別參數可以是「任何型別」,但有時需要它至少具備某些性質——用 extends 約束:
// ❌ 沒約束:T 可能沒有 .length
function longest<T>(a: T, b: T): T {
return a.length > b.length ? a : b; // 錯:T 不保證有 length
}
// ✅ 約束 T 必須有 length 屬性
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length > b.length ? a : b;
}
longest("ab", "abc"); // ✅ string 有 length
longest([1], [1, 2]); // ✅ array 有 length
longest(1, 2); // ❌ number 沒有 length
T extends X的意思是「T 必須是 X 的子型別(至少滿足 X)」,不是繼承類別。
搭配 keyof 取屬性
泛型 + keyof 是經典組合,做出型別安全的屬性存取:
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: "Alice" };
const name = getProp(user, "name"); // 推論為 string
const id = getProp(user, "id"); // 推論為 number
getProp(user, "email"); // ❌ "email" 不是 user 的 key
keyof T= T 所有屬性名組成的 union(這裡是"id" | "name")K extends keyof T約束 key 必須是 T 真實存在的屬性T[K]是索引存取型別:取出該屬性的型別
預設型別參數
型別參數可給預設值,呼叫時不指定就用預設:
interface ApiResponse<T = unknown> {
data: T;
status: number;
}
const r1: ApiResponse = { data: "x", status: 200 }; // T = unknown
const r2: ApiResponse<number> = { data: 1, status: 200 }; // T = number
多個型別參數
function zip<T, U>(a: T[], b: U[]): [T, U][] {
return a.map((item, i) => [item, b[i]]);
}
const pairs = zip(["a", "b"], [1, 2]); // [string, number][]
慣例命名:T(Type)、K(Key)、V(Value)、E(Element)、R(Return)。但語意清楚時,用有意義的名字(如 TData)更好讀。
實戰範例
1. API 回應包裝
interface Result<T> {
success: boolean;
data: T;
error?: string;
}
async function fetchUser(id: number): Promise<Result<User>> {
// ...
return { success: true, data: user };
}
2. 型別安全的事件系統
type EventMap = {
click: { x: number; y: number };
message: { text: string };
};
function emit<K extends keyof EventMap>(event: K, payload: EventMap[K]) {
// event 與 payload 型別自動對應
}
emit("click", { x: 1, y: 2 }); // ✅
emit("message", { x: 1 }); // ❌ payload 型別不符
3. 約束 + 預設值組合
function createMap<K extends string | number, V = string>(): Map<K, V> {
return new Map<K, V>();
}
常見陷阱
1. 把泛型誤當成 any
// ❌ 這沒有真正利用泛型——回傳沒關聯到參數
function bad<T>(x: T): any { return x; }
// ✅ 型別參數要貫穿輸入到輸出
function good<T>(x: T): T { return x; }
2. 過度泛型化
不是所有東西都該泛型。只在型別真的需要重用或關聯時才用;單一具體型別就夠時,泛型只是增加複雜度。
3. 約束太鬆或太緊
約束(extends)要剛好夠用:太鬆會在函式內無法安全操作;太緊會限制可用的引數。
常見問題
問題 1:泛型和 any 差在哪?
any 完全放棄型別;泛型是「把型別當參數傳遞」——呼叫時代入具體型別,輸入到輸出的型別關係被保留,型別安全不丟失。
問題 2:T extends X 是繼承嗎?
不是類別繼承。它是型別約束:「T 必須是 X 的子型別 / 至少滿足 X 的形狀」。用來保證在泛型內可以安全使用 X 的性質。
問題 3:什麼時候該用泛型?
當函式/型別要對多種型別重用,且輸入與輸出(或多個參數)之間有型別關聯時。若只是單一固定型別,直接寫具體型別更簡單。
問題 4:型別參數一定要寫 <T> 嗎?
呼叫時多半能自動推論,不必手寫。只有推不出(如沒有引數可推)或想覆寫推論結果時才明確指定。
問題 5:keyof 和泛型怎麼搭?
K extends keyof T 約束 key 必須是物件 T 真實存在的屬性,配合索引存取型別 T[K] 取出對應屬性的型別——做出型別安全的屬性存取/事件系統。
總結
核心要點
- 泛型 = 把型別當參數:彈性重用 + 保留型別安全(與
any相反) - 函式、介面、型別別名、類別都能泛型化,型別參數多半可自動推論
- 約束
T extends X:要求型別參數至少滿足某形狀(非類別繼承) K extends keyof T+T[K]:型別安全的屬性存取,經典組合- 可給預設型別參數、用多個型別參數
- 別把泛型寫成 any(型別要貫穿輸入輸出)、別過度泛型化
快速參考
| 語法 | 意義 |
|---|---|
<T> |
宣告型別參數 |
T extends X |
約束:T 須滿足 X |
keyof T |
T 的屬性名 union |
T[K] |
索引存取:取屬性型別 |
<T = D> |
預設型別參數 |
建立日期:2026-06-18