測驗:TypeScript 泛型入門
共 5 題,點選答案後會立即顯示結果
1. 泛型(Generics)最核心的概念是什麼?
2. 在泛型命名慣例中,<K, V> 通常代表什麼?
3. 以下程式碼中,Promise<User> 代表什麼意思?
const result: Promise<User> = fetchUser();
4. 以下泛型函式的作用是什麼?
function swap<T, U>(a: T, b: U): [U, T] {
return [b, a];
}
5. 以下程式碼中,呼叫 getLength(123) 會發生什麼事?
function getLength<T extends { length: number }>(arg: T): number {
return arg.length;
}
一句話說明
泛型就是「型別的參數」,讓同一段程式碼能對應不同的型別。
為什麼需要泛型?
假設 AI 幫你寫了一個「把東西包進陣列」的函式:
function wrapInArray(value: string): string[] {
return [value];
}
這只能包字串。如果你也想包數字呢?難道要再寫一份?
function wrapNumberInArray(value: number): number[] {
return [value];
}
這太蠢了。泛型就是解決這個問題的:寫一次,適用所有型別。
function wrapInArray<T>(value: T): T[] {
return [value];
}
Code language: HTML, XML (xml)翻譯:「這個函式接收一個某種型別 T 的值,回傳一個 T 陣列。至於 T 是什麼,用的時候再決定。」
最小範例
function identity<T>(arg: T): T {
return arg;
}
Code language: JavaScript (javascript)這是最經典的泛型範例。逐行翻譯:
function identity<T>(arg: T): T {
// ↑ ↑ ↑
// 宣告型別參數 T | 回傳型別也是 T
// 參數型別是 T
return arg;
}
Code language: JavaScript (javascript)白話翻譯:「這個函式接收什麼型別,就回傳什麼型別。」
使用時,TypeScript 會自動推斷 T 是什麼:
const a = identity("hello"); // T 被推斷為 string,a 是 string
const b = identity(42); // T 被推斷為 number,b 是 number
Code language: JavaScript (javascript)你也可以手動指定:
const a = identity<string>("hello"); // 明確指定 T 是 string
Code language: JavaScript (javascript)逐行翻譯:解讀 <T> 語法
在 AI 產生的程式碼裡,你會大量看到角括號 <> 搭配大寫字母。以下是翻譯指南:
function getFirst<T>(list: T[]): T {
// ↑ ↑ ↑
// 「我要用到一種型別,暫時叫它 T」
// 「參數是 T 組成的陣列」
// 「回傳一個 T」
return list[0];
}
Code language: PHP (php)你可以把 <T> 想成函式的「型別參數」,就像 (arg: T) 是值的參數一樣。
常見的泛型命名慣例
| 名稱 | 代表的意思 | 常出現在 |
|---|---|---|
T |
Type(型別) | 通用場景 |
K |
Key(鍵) | 物件的 key |
V |
Value(值) | 物件的 value |
U |
第二個型別 | 需要兩個型別參數時 |
E |
Element(元素) | 集合、陣列 |
看到 <K, V> 就知道是在處理「鍵值對」,看到 <T, U> 就是在處理「兩種型別」。
常見變化
AI 產生的程式碼中,泛型最常出現在以下幾種情境。
變化 1:內建泛型型別
這些你一定會遇到,必須認得:
const names: Array<string> = ["Alice", "Bob"];
// 翻譯:「一個裝 string 的陣列」
// 等同於 string[]
const response: Promise<Response> = fetch("/api/users");
// 翻譯:「一個未來會拿到 Response 的 Promise」
const settings: Record<string, number> = { width: 100, height: 200 };
// 翻譯:「key 是 string、value 是 number 的物件」
Code language: JavaScript (javascript)速查表:
| 你會看到 | 意思 |
|---|---|
Array<string> |
字串陣列,等同 string[] |
Promise<User> |
未來會拿到 User 的非同步結果 |
Record<string, number> |
一個 key 是字串、value 是數字的物件 |
Map<string, User> |
一個字串對應 User 的 Map |
Set<number> |
一組不重複的數字 |
Partial<User> |
User 的所有欄位都變成可選 |
Pick<User, "name"> |
只取 User 的 name 欄位 |
Omit<User, "password"> |
User 去掉 password 欄位 |
變化 2:泛型箭頭函式
AI 常用箭頭函式寫泛型:
const getFirst = <T>(list: T[]): T => list[0];
// 翻譯:「取得陣列的第一個元素,型別跟陣列元素一樣」
Code language: PHP (php)注意:在 .tsx 檔案裡(React),你可能看到這種寫法,多了一個逗號:
const getFirst = <T,>(list: T[]): T => list[0];
// ↑ 這個逗號是為了避免 JSX 解析器把 <T> 當成 HTML 標籤
Code language: PHP (php)變化 3:多個型別參數
function swap<T, U>(a: T, b: U): [U, T] {
return [b, a];
}
// 翻譯:「接收兩個不同型別的值,交換順序回傳」
const result = swap("hello", 42); // result 的型別是 [number, string]
Code language: HTML, XML (xml)變化 4:泛型 interface 和 type
AI 最常用泛型來定義 API 回應格式:
// 定義通用的 API 回應結構
interface ApiResponse<T> {
data: T; // 實際資料,型別由 T 決定
status: number; // HTTP 狀態碼
message: string; // 訊息
}
// 使用時指定 T 是什麼
type UserResponse = ApiResponse<User>;
// 翻譯:「一個 API 回應,裡面的 data 是 User 型別」
type ProductListResponse = ApiResponse<Product[]>;
// 翻譯:「一個 API 回應,裡面的 data 是 Product 陣列」
Code language: PHP (php)白話翻譯:「ApiResponse 是一個回應的模板,T 是可以替換的部分。」
用 type 也可以做一樣的事:
type Result<T> = {
success: boolean;
data: T | null;
error: string | null;
};
Code language: JavaScript (javascript)泛型約束:extends 的用法
有時候泛型不能是「任何型別」,需要限制。這時用 extends:
function getLength<T extends { length: number }>(arg: T): number {
return arg.length;
}
// 翻譯:「T 必須有 length 屬性,這個函式回傳它的長度」
Code language: JavaScript (javascript)使用效果:
getLength("hello"); // OK,string 有 length
getLength([1, 2, 3]); // OK,陣列有 length
getLength(123); // 錯誤!number 沒有 length
Code language: JavaScript (javascript)常見的約束寫法
// 約束 T 必須是某個 interface 的子集
function printName<T extends { name: string }>(obj: T) {
console.log(obj.name);
}
// 翻譯:「接收任何有 name 屬性的物件」
// 約束 K 必須是 T 的某個 key
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// 翻譯:「從物件取出指定 key 的值,key 必須是物件真的有的 key」
Code language: HTML, XML (xml)keyof T 的意思是「T 的所有 key 的集合」。所以 K extends keyof T 就是「K 必須是 T 的某個 key」。
const user = { name: "Alice", age: 25 };
getProperty(user, "name"); // OK,回傳 string
getProperty(user, "age"); // OK,回傳 number
getProperty(user, "email"); // 錯誤!user 沒有 email
Code language: JavaScript (javascript)實際場景:AI 常怎麼用泛型
場景 1:通用的 API 請求函式
async function fetchApi<T>(url: string): Promise<T> {
const response = await fetch(url);
const data: T = await response.json();
return data;
}
// 使用時指定回傳型別
const user = await fetchApi<User>("/api/user/1");
const products = await fetchApi<Product[]>("/api/products");
Code language: JavaScript (javascript)翻譯:「fetchApi 是一個通用的 API 呼叫函式。用的時候告訴它回傳什麼型別,它就會幫你做型別檢查。」
場景 2:React 的 useState
如果你有碰到 React,這是最常見的泛型用法:
const [user, setUser] = useState<User | null>(null);
// 翻譯:「建立一個 state,值可以是 User 或 null,初始值是 null」
Code language: JavaScript (javascript)場景 3:通用的工具型別
type Nullable<T> = T | null;
// 翻譯:「Nullable<User> 就是 User | null」
type AsyncFunction<T> = () => Promise<T>;
// 翻譯:「一個回傳 Promise<T> 的函式」
Code language: JavaScript (javascript)必看懂 vs 知道就好
必看懂(會一直出現)
<T>在函式或型別後面:宣告泛型參數Array<string>、Promise<User>:內建泛型型別ApiResponse<T>這類自訂泛型 interface<T extends SomeType>:泛型約束
知道就好(遇到再查)
- 條件型別:
T extends U ? X : Y,型別版的三元運算 - infer 關鍵字:
T extends Promise<infer U> ? U : T,從型別裡提取子型別 - 映射型別:
{ [K in keyof T]: ... },批量轉換型別 - 模板字面量型別:`
${T}Changed,用字串模板生成型別 這些進階用法在函式庫的型別定義裡會出現,但日常開發中不需要自己寫。遇到時知道「這是在做型別運算」就夠了。 --- ## Vibe Coder 檢查點 看到泛型程式碼時確認: - [ ]裡的 T 代表什麼?能猜出來嗎?(看命名和上下文) - [ ] 使用泛型函式時,T 是怎麼被決定的?(自動推斷還是手動指定) - [ ] 如果有extends約束,T 被限制成什麼範圍? - [ ] AI 定義的泛型 interface(像 ApiResponse<T>),T 在實際使用時被替換成了什麼? --- ## 總結 泛型的核心概念只有一個:**型別也可以當參數傳**。 | 概念 | 程式碼 | 白話翻譯 | |------|--------|---------| | 泛型函式 |function fn(arg: T): T | 進什麼型別,出什麼型別 | | 泛型 interface |interface Box{ value: T } | 一個模板,T 可以替換 | | 內建泛型 |Array| 裝字串的陣列 | | 泛型約束 || T 必須有 name 屬性 | | 多型別參數 || 兩個型別參數,通常是 key 和 value | 下次看到 AI 寫出` 時,別慌。把 T 想成「型別的參數」,就像函式的參數一樣,用的時候再填入實際的型別就好了。
進階測驗:TypeScript 泛型入門
測驗目標:驗證你是否能在實際情境中應用所學。
共 5 題,包含情境題與錯誤診斷題。
共 5 題,包含情境題與錯誤診斷題。
1. 你正在開發一個後端 API,需要為不同的資源(User、Product、Order)設計統一的回應格式,都包含 data、status、message 三個欄位,但 data 的型別會隨資源不同而改變。最佳的做法是什麼? 情境題
2. 你需要寫一個工具函式,從任意物件中安全地取出某個屬性的值。你希望 TypeScript 在編譯期就能檢查 key 是否合法,而且回傳值的型別要精確。以下哪種寫法最合適? 情境題
3. 你在 React 專案的 .tsx 檔案中寫了一個泛型箭頭函式,但編輯器報出語法錯誤。以下是你的程式碼:情境題
// MyComponent.tsx
const getFirst = <T>(list: T[]): T => list[0];
最可能的原因和解決方式是什麼?
4. 以下程式碼在編譯時報錯,問題出在哪裡? 錯誤診斷
function logLength<T>(arg: T): void {
console.log(arg.length); // Error: Property ‘length’ does not exist on type ‘T’
}
logLength(“hello”);
logLength([1, 2, 3]);
5. 以下程式碼的型別定義有什麼問題? 錯誤診斷
interface ApiResponse {
data: any;
status: number;
message: string;
}
const userRes: ApiResponse = { data: { name: “Alice” }, status: 200, message: “OK” };
// 之後使用 userRes.data 時沒有任何型別提示
console.log(userRes.data.name); // 能執行,但 TypeScript 不會檢查
console.log(userRes.data.email); // 也能執行,但 email 根本不存在!