日常程式碼中的 TypeScript 泛型:實用指南

Frontend2026-10-04TryQuickToolBox

你正在撰寫一個可處理任何型別的函式,但 TypeScript 一直抱怨 any。或者你為了處理不同型別,把同一個函式複製了三遍。泛型可以解決這個問題——它們讓你撰寫可處理多種型別的程式碼,同時保持型別安全。但許多開發者不是避開它們,就是過度使用。本指南將展示如何在日常程式碼中使用泛型,而不會頭痛。

什麼是泛型,為什麼你該關心?

泛型是一種參數化型別的方式。與其硬編碼像 string 或 number 這樣的型別,你可以使用一個型別變數(通常是 T),在函式或類別被使用時,它會被替換為具體的型別。

這為什麼重要?因為它讓你撰寫可重複使用且保持型別安全的程式碼。沒有泛型,你要麼複製程式碼,要麼使用 any,這就違背了 TypeScript 的初衷。

泛型函式:建構基礎

讓我們從一個簡單的例子開始。假設你想要一個回傳陣列第一個元素的函式。

function firstElement(arr: any[]): any {
  return arr[0];
}

這可行,但你失去了型別資訊。如果你傳入一個字串陣列,回傳型別是 any,而不是 string。現在使用泛型:

function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

現在 TypeScript 知道如果你傳入 string[],你會得到 string | undefined。型別被保留了。

你也可以使用多個型別參數:

function pair<A, B>(a: A, b: B): [A, B] {
  return [a, b];
}

泛型介面和型別別名

泛型不僅限於函式。你可以建立泛型介面和型別別名來描述靈活的資料結構。

interface Box<T> {
  value: T;
}

const stringBox: Box<string> = { value: "hello" };
const numberBox: Box<number> = { value: 42 };

這對 API 回應特別有用。與其為每個端點定義一個新介面,你可以定義一個泛型 ApiResponse<T>:

interface ApiResponse<T> {
  data: T;
  status: number;
}

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

const response: ApiResponse<User> = {
  data: { id: 1, name: "Alice" },
  status: 200,
};

泛型約束:加入規則

有時你想限制泛型可以使用的型別。這就是約束的用途。使用 extends 關鍵字要求型別具有某些屬性。

function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength("hello"); // OK
getLength([1, 2, 3]); // OK
getLength(42); // Error: number doesn't have length

約束讓你的泛型更可預測,並防止執行階段錯誤。

泛型類別

類別也可以是泛型的。這對堆疊、佇列或儲存庫等資料結構很方便。

class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
const top = numberStack.pop(); // number | undefined

何時使用泛型(以及何時不該使用)

泛型很強大,但如果過度使用,會讓程式碼更難閱讀。這裡有一個快速指南:

使用泛型的時機 避免泛型的時機
你需要保留型別關係 簡單的聯合型別或 any 就足夠
你正在撰寫可重複使用的工具 型別總是相同
你想避免程式碼重複 它讓程式碼更難理解

一個好的經驗法則:如果你發現自己為多種型別撰寫相同的邏輯,考慮使用泛型。如果你正在加入第三個型別參數,而且沒有人能理解,那就退一步。

常見模式與最佳實踐

真實世界範例:泛型 API 客戶端

讓我們用一個實際例子來總結:一個簡單的 API 客戶端,它獲取資料並回傳具型別的回應。

async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return response.json() as Promise<T>;
}

interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

async function main() {
  const post = await fetchData<Post>("https://jsonplaceholder.typicode.com/posts/1");
  console.log(post.title); // string
}

這種模式在前端程式碼中很常見。你獲得型別安全,而不需要為每個端點重複自己。

常見問題

TypeScript 中的 <T> 語法是什麼意思?

<T> 宣告一個型別參數。它是一個佔位符,代表將在函式、介面或類別被使用時指定的型別。你可以隨意命名,但 T 是單一型別的慣例。

我可以使用多個泛型型別參數嗎?

可以。你可以定義任意多個,用逗號分隔:<T, U, V>。使用描述性名稱來保持程式碼可讀性。

如何將泛型約束為只接受物件?

使用 T extends object。這確保 T 是物件型別,而不是像 string 或 number 這樣的原始型別。你也可以約束為特定形狀,例如 T extends { id: number }。

泛型是撰寫可維護 TypeScript 的核心部分。如果你正在處理 JSON 資料,並想快速驗證或格式化,試試我們的 JSON Formatter 來美化列印和檢查你的 API 回應。