昨天我們聊了函式型別,今天要升級一下,
進入 TypeScript 型別系統中一個超強的功能——泛型(Generics)。
如果你覺得型別一旦定義就很死板,那泛型會讓你眼睛一亮:
它讓型別變成「變數」一樣,可以傳進去再決定。
先看一個不用泛型的情況:
function identityString(value: string): string {
return value;
}
function identityNumber(value: number): number {
return value;
}
如果我們想讓這個函式能處理任何型別,只能寫一堆重複的版本,超麻煩。
這時候泛型就能出場。
function identity<T>(value: T): T {
return value;
}
identity<string>("Hello");
identity<number>(123);
<T>
:泛型參數(可以取任何名字,但 T 很常見)<string>
表示這次的 T 是 string
其實不用每次都手動寫 <T>
,TS 會自己推:
let str = identity("Hello"); // T = string
let num = identity(123); // T = number
function getFirst<T>(arr: T[]): T {
return arr[0];
}
let firstNum = getFirst([1, 2, 3]); // 推論 T = number
let firstStr = getFirst(["a", "b"]); // 推論 T = string
有時候我們不想讓 T 隨便是什麼型別,可以用 extends
約束它:
function getLength<T extends { length: number }>(item: T): number {
return item.length;
}
getLength("Hello"); // OK
getLength([1, 2, 3]); // OK
getLength(123); // ❌ Number 沒有 length 屬性
function merge<T, U>(a: T, b: U): T & U {
return { ...a, ...b };
}
let merged = merge({ name: "Alice" }, { age: 25 });
// merged 型別推論為 { name: string } & { age: number }
type ApiResponse<T> = {
data: T;
status: number;
};
interface Repository<T> {
getAll(): T[];
getById(id: string): T;
}
let userRepo: Repository<{ id: string; name: string }> = {
getAll: () => [{ id: "u1", name: "Bob" }],
getById: (id) => ({ id, name: "Bob" })
};
function fetchData<T>(url: string): Promise<T> {
return fetch(url).then(res => res.json());
}
fetchData<{ id: string; name: string }>("/api/user")
.then(user => console.log(user.name));
好處:
function printLength<T>(item: T) {
console.log(item.length); // ❌ Property 'length' does not exist on type 'T'
}
解法:
function printLength<T extends { length: number }>(item: T) {
console.log(item.length);
}
不是每個地方都需要泛型,簡單型別直接用 string
、number
反而更清楚。