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 }));