TypeScript 泛型 (Generics)

泛型 (Generics) 是 TypeScript 中一個強大的功能,它允許我們在定義函式、介面或類別時使用型別參數,讓程式碼可以適用於多種型別,同時保持型別安全。

為什麼需要泛型?

假設我們要寫一個回傳輸入值的函式:

// 不使用泛型,只能針對特定型別
function identityNumber(value: number): number {
  return value;
}

function identityString(value: string): string {
  return value;
}

// 使用 any 會失去型別檢查
function identityAny(value: any): any {
  return value;
}

// 使用泛型,保持型別安全又靈活
function identity<T>(value: T): T {
  return value;
}

let num = identity<number>(42); // num 是 number
let str = identity<string>('hello'); // str 是 string
let auto = identity('world'); // TypeScript 自動推論 T 為 string

泛型函式

基本語法

function functionName<T>(param: T): T {
  return param;
}

多個型別參數

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

let result = pair<string, number>('age', 25); // [string, number]
let auto = pair('name', '小明'); // [string, string]

泛型陣列

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

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

console.log(getFirst([1, 2, 3])); // 1
console.log(getLast(['a', 'b', 'c'])); // "c"

泛型約束 (Generic Constraints)

使用 extends 限制泛型的範圍:

interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(value: T): T {
  console.log(value.length);
  return value;
}

logLength('hello'); // OK,string 有 length
logLength([1, 2, 3]); // OK,array 有 length
logLength({ length: 5 }); // OK,有 length 屬性
// logLength(123);        // 錯誤,number 沒有 length

使用 keyof 約束

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

const user = { name: '小明', age: 25 };
let name = getProperty(user, 'name'); // string
let age = getProperty(user, 'age'); // number
// getProperty(user, "invalid");       // 錯誤

多重約束

interface Named {
  name: string;
}
interface Aged {
  age: number;
}

function printPerson<T extends Named & Aged>(person: T): void {
  console.log(`${person.name}, ${person.age} 歲`);
}

printPerson({ name: '小明', age: 25 });
// printPerson({ name: "小明" });  // 錯誤,缺少 age

泛型介面

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

class Box<T> implements Container<T> {
  constructor(public value: T) {}

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

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

const numberBox = new Box<number>(42);
const stringBox = new Box<string>('hello');

泛型函式介面

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

const stringToNumber: Transformer<string, number> = (s) => parseInt(s, 10);
const numberToString: Transformer<number, string> = (n) => n.toString();

泛型類別

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

const stringStack = new Stack<string>();
stringStack.push('a');
stringStack.push('b');

泛型型別別名

type Nullable<T> = T | null;
type Optional<T> = T | undefined;
type Result<T, E> = { success: true; value: T } | { success: false; error: E };

let name: Nullable<string> = '小明';
name = null; // OK

type ApiResult<T> = Result<T, string>;

function fetchUser(): ApiResult<User> {
  // ...
}

泛型預設值

TypeScript 3.0+ 支援泛型預設值:

interface Config<T = string> {
  value: T;
}

let config1: Config = { value: 'hello' }; // T 預設為 string
let config2: Config<number> = { value: 42 }; // 明確指定 T 為 number

// 函式也可以有預設值
function createArray<T = string>(length: number, value: T): T[] {
  return Array(length).fill(value);
}

條件型別中的泛型

type TypeName<T> = T extends string
  ? 'string'
  : T extends number
    ? 'number'
    : T extends boolean
      ? 'boolean'
      : T extends undefined
        ? 'undefined'
        : T extends Function
          ? 'function'
          : 'object';

type T1 = TypeName<string>; // "string"
type T2 = TypeName<number>; // "number"
type T3 = TypeName<() => void>; // "function"

infer 關鍵字

用於從型別中推論:

// 推論函式回傳型別
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function greet(): string {
  return 'hello';
}

type GreetReturn = ReturnType<typeof greet>; // string

// 推論陣列元素型別
type ArrayElement<T> = T extends (infer E)[] ? E : never;

type StringElement = ArrayElement<string[]>; // string
type NumberElement = ArrayElement<number[]>; // number

// 推論 Promise 的結果型別
type Awaited<T> = T extends Promise<infer U> ? U : T;

type PromiseString = Awaited<Promise<string>>; // string

泛型工具函式

map 函式

function map<T, U>(arr: T[], fn: (item: T, index: number) => U): U[] {
  return arr.map(fn);
}

const numbers = [1, 2, 3];
const strings = map(numbers, (n) => n.toString()); // string[]

filter 函式

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

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

reduce 函式

function reduce<T, U>(arr: T[], fn: (acc: U, item: T) => U, initial: U): U {
  return arr.reduce(fn, initial);
}

const sum = reduce([1, 2, 3], (acc, n) => acc + n, 0); // number

實用範例

通用 API 回應處理

interface ApiResponse<T> {
  success: boolean;
  data: T;
  message?: string;
  timestamp: number;
}

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

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

// 使用
const userResponse = await fetchData<User>('/api/user/1');
console.log(userResponse.data.name);

事件發射器

class EventEmitter<Events extends Record<string, any>> {
  private listeners: { [K in keyof Events]?: ((data: Events[K]) => void)[] } = {};

  on<K extends keyof Events>(event: K, listener: (data: Events[K]) => void): void {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event]!.push(listener);
  }

  emit<K extends keyof Events>(event: K, data: Events[K]): void {
    this.listeners[event]?.forEach((listener) => listener(data));
  }
}

// 定義事件型別
interface AppEvents {
  login: { userId: string };
  logout: void;
  message: { text: string; from: string };
}

const emitter = new EventEmitter<AppEvents>();

emitter.on('login', (data) => {
  console.log(`User ${data.userId} logged in`);
});

emitter.emit('login', { userId: '123' });

狀態管理

class Store<State> {
  private state: State;
  private listeners: ((state: State) => void)[] = [];

  constructor(initialState: State) {
    this.state = initialState;
  }

  getState(): State {
    return this.state;
  }

  setState(updater: (state: State) => State): void {
    this.state = updater(this.state);
    this.listeners.forEach((listener) => listener(this.state));
  }

  subscribe(listener: (state: State) => void): () => void {
    this.listeners.push(listener);
    return () => {
      this.listeners = this.listeners.filter((l) => l !== listener);
    };
  }
}

// 使用
interface AppState {
  user: { name: string } | null;
  count: number;
}

const store = new Store<AppState>({ user: null, count: 0 });

store.subscribe((state) => {
  console.log('State changed:', state);
});

store.setState((state) => ({ ...state, count: state.count + 1 }));