跳至主要內容
Skip to content

TypeScript 泛型基礎:函式、介面、類別

泛型(Generics)是 TypeScript 最強大的特性之一。本篇將帶你從零開始掌握泛型的核心概念。


一、 為什麼需要泛型?

1.1 問題場景

typescript
// 這個函式只能處理 number
function identityNumber(value: number): number {
  return value;
}

// 要處理 string 得再寫一個
function identityString(value: string): string {
  return value;
}

// 每種類型都寫一個?太麻煩了!

1.2 用 any?不安全

typescript
function identity(value: any): any {
  return value;
}

const result = identity("hello");
// result 是 any,失去了類型資訊
result.toUpperCase(); // 沒有自動完成提示

1.3 泛型解決方案

typescript
function identity<T>(value: T): T {
  return value;
}

const str = identity("hello"); // str: string
const num = identity(42); // num: number

str.toUpperCase(); // 有自動完成!

二、 泛型函式

2.1 基本語法

typescript
// <T> 是類型參數
function identity<T>(value: T): T {
  return value;
}

// 明確指定類型
const a = identity<string>("hello");

// 類型推論(推薦)
const b = identity("hello"); // 自動推論為 string

2.2 多個類型參數

typescript
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

const result = pair("hello", 42); // [string, number]

2.3 箭頭函式

typescript
// 一般寫法
const identity = <T>(value: T): T => {
  return value;
};

// 在 TSX 中需要加逗號避免與 JSX 混淆
const identity2 = <T>(value: T): T => value;

三、 泛型介面

3.1 基本用法

typescript
interface Container<T> {
  value: T;
  getValue(): T;
}

const stringContainer: Container<string> = {
  value: "hello",
  getValue() {
    return this.value;
  },
};

3.2 API 回應

typescript
interface ApiResponse<T> {
  success: boolean;
  data: T;
  error?: string;
}

interface User {
  id: number;
  name: string;
}

async function fetchUser(): Promise<ApiResponse<User>> {
  // ...
}

const response = await fetchUser();
if (response.success) {
  console.log(response.data.name); // 有類型提示!
}

3.3 泛型函式類型

typescript
interface Transformer<T, U> {
  (input: T): U;
}

const stringToNumber: Transformer<string, number> = (str) => {
  return parseInt(str, 10);
};

四、 泛型類別

4.1 基本用法

typescript
class Box<T> {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  getValue(): T {
    return this.value;
  }

  setValue(value: T): void {
    this.value = value;
  }
}

const stringBox = new Box("hello");
const numberBox = new Box(42);

4.2 泛型堆疊

typescript
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];
  }

  isEmpty(): boolean {
    return this.items.length === 0;
  }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 2

五、 泛型 type

5.1 基本用法

typescript
type Result<T> =
  | {
      success: true;
      data: T;
    }
  | {
      success: false;
      error: string;
    };

function handleResult<T>(result: Result<T>) {
  if (result.success) {
    console.log(result.data);
  } else {
    console.error(result.error);
  }
}

5.2 泛型工具類型

typescript
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
type Maybe<T> = T | null | undefined;

let name: Nullable<string> = null;
name = "John";

六、 實用範例

6.1 陣列操作

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

function last<T>(arr: T[]): T | undefined {
  return arr[arr.length - 1];
}

function filter<T>(arr: T[], predicate: (item: T) => boolean): T[] {
  return arr.filter(predicate);
}

const numbers = [1, 2, 3, 4, 5];
const firstNum = first(numbers); // number | undefined
const evens = filter(numbers, (n) => n % 2 === 0); // number[]

6.2 物件操作

typescript
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: "John", age: 30 };
const name = getProperty(user, "name"); // string
const age = getProperty(user, "age"); // number
// getProperty(user, 'email')  // 錯誤!'email' 不是有效的 key

6.3 Promise 包裝

typescript
async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  return response.json();
}

interface User {
  id: number;
  name: string;
}

const user = await fetchData<User>("/api/user");
console.log(user.name); // 有類型提示

七、 常見錯誤

7.1 忘記使用類型參數

typescript
// 錯誤:T 沒有被使用
function bad<T>(value: string): string {
  return value;
}

// 正確
function good<T>(value: T): T {
  return value;
}

7.2 過度使用泛型

typescript
// 不需要泛型
function unnecessary<T extends string>(str: T): string {
  return str.toUpperCase();
}

// 直接用 string
function better(str: string): string {
  return str.toUpperCase();
}

總結

語法說明範例
<T>類型參數function fn<T>()
多參數多個類型<T, U, V>
推論自動判斷fn('hello')
明確手動指定fn<string>('hello')

> **使用時機**:

  • 函式輸入輸出類型相關
  • 容器類型(陣列、集合)
  • 需要保留類型資訊

進階挑戰

  1. 實作一個泛型 swap 函式,交換元組的兩個元素
  2. 實作一個泛型 Queue<T> 類別
  3. localStorage 封裝一個類型安全的包裝函式
typescript
// 練習 3 提示
function getItem<T>(key: string): T | null {
  const item = localStorage.getItem(key);
  return item ? JSON.parse(item) : null;
}

延伸閱讀與資源