跳至主要内容

泛型介紹

什麼是泛型?

首先先看一個簡單的例子

function identity<T>(arg: T): Type {
return arg;
}
const stringOutput = identity<string>("Hello World");
const numberOutput = identity<number>(25);

這是一個泛型函式,主要由兩個部分構成:

  1. Type Parameter: 這邊是用 T,可以使用任意名稱。但通常習慣使用 T 表示,代表 "Type" 。這個參數允許我們在撰寫程式時保持型別彈性。

    除了 T 以外,以下是一些常見的泛型參數:

    • U:當你需要多個泛型參數時,會使用 U 來表示第二個型別參數。
    • K(Key):物件鍵值的泛型
    • V(Value):表示與 K 配對的值的型別。這個泛型參數通常與 K 搭配,定義物件中鍵對應的值的型別。
  2. Type Argument : 泛型函式使用時,我們將具體的型別傳入函式中,例如 stringnumber,使得程式碼可以適應不同型別的需求。

為什麼要用泛型?

  1. 安全性 : 雖然用 any 可以解決部份問題,但泛型讓我們可以編寫適用於不同類型數據的代碼,仍然保持 TypeScript 的類型安全性,避免了丟失型別檢查的風險。
  2. 可重用性:泛型可以撰寫適用於多種型別的程式碼,避免為每個型別重複撰寫不同的函式。
  3. 可維護性: 因為寫的 code 更少,也比較容易維護。

常用關鍵字介紹

typeof

typeof 關鍵字可以用來取得一個變數或表達式的型別。

const user = {
name: "John",
age: 30,
};

type UserType = typeof user; // { name: string; age: number; }

keyof

keyof 可以取得一個物件的所有 key,然後組成 Union Type。

type User = {
name: string;
age: number;
};

type UserKeys = keyof User; // 'name' | 'age'

in

in 是映射型別(Mapped Types)的語法,用來遍歷型別的所有屬性鍵,並根據這些鍵來創建或修改型別。

type Permissions = "read" | "write" | "delete";
type PermissionFlags = { [P in Permissions]: boolean };
// { read: boolean; write: boolean; delete: boolean }

extends

我們可以透過 extends 將泛型限制於某些特定型別。舉個例子,如果我們想限制 identity 函式只能接受 numberstring

function identity<T extends number | string>(arg: T): T {
return arg;
}
const stringOutput = identity<string>("Hello World");
const numberOutput = identity<number>(25);

這樣我們就限制了 T 必須是 numberstring 型別。

extends 除了限制型別外,也可以用來確保傳入的物件具備特定屬性,會在接下來的例子說明。

Condition Type 和 infer

Condition Type

語法基本會長這樣

T extends U ? X : Y

TU 是型別,T extends U 是條件判斷。如果 TU 的子型別,則結果為 X。否則,結果為 Y

範例:

type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false

條件型別也可以疊加使用,進行複雜的邏輯處理:

type Result<T> = T extends string
? { text: T }
: T extends number
? { value: T }
: never;

infer

infer 需要搭配 extends 和 Condition Type 使用。它可以在條件型別中推斷出某個特定的型別變數。

type ElementType<T> = T extends (infer U)[] ? U : T;

infer U 會嘗試推斷陣列中的元素型別,並將其存儲在變數 U 中。

另外 infer 也經常會搭配 never 來使用,算是一種 TypeScript 表達錯誤的方式或是表示某個型別不應該存在。

參考資料:

延伸閱讀: