强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

TypeScript 开发指南 / 07 - 泛型

泛型

泛型(Generics)是 TypeScript 中最强大的特性之一,它允许你编写可复用的、类型安全的代码。

为什么需要泛型?

// 不使用泛型:需要为每种类型写重复代码
function getFirstNumber(arr: number[]): number {
  return arr[0];
}
function getFirstString(arr: string[]): string {
  return arr[0];
}

// 使用 any:丢失类型信息
function getFirstAny(arr: any[]): any {
  return arr[0]; // 返回值类型是 any,不安全
}

// 使用泛型:类型安全且可复用
function getFirst<T>(arr: T[]): T {
  return arr[0];
}

const num = getFirst([1, 2, 3]);       // 推断为 number
const str = getFirst(["a", "b", "c"]); // 推断为 string

泛型函数

基本语法

// 单类型参数
function identity<T>(value: T): T {
  return value;
}

// 显式指定类型
identity<string>("hello");

// 类型推断(推荐)
identity(42);       // 推断 T = number
identity("hello");  // 推断 T = string

多类型参数

// 两个类型参数
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

const p = pair("Alice", 25); // [string, number]
const p2 = pair(1, true);   // [number, boolean]

// 对象合并
function merge<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

const merged = merge({ name: "Alice" }, { age: 25 });
// 类型: { name: string } & { age: number }

泛型箭头函数

const getFirst = <T>(arr: T[]): T => arr[0];
const map = <T, U>(arr: T[], fn: (item: T) => U): U[] => arr.map(fn);

泛型约束(Generic Constraints)

使用 extends 关键字约束泛型参数:

// 约束泛型必须有 length 属性
interface HasLength {
  length: number;
}

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

logLength("hello");      // ✅ string 有 length
logLength([1, 2, 3]);    // ✅ 数组有 length
logLength({ length: 5 }); // ✅ 有 length 属性
// logLength(123);        // ❌ number 没有 length

keyof 约束

// 约束 K 必须是 T 的键
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: "Alice", age: 25 };
const name = getProperty(user, "name"); // string
const age = getProperty(user, "age");   // number
// const err = getProperty(user, "email"); // ❌ "email" 不是 keyof User

多重约束

interface Printable {
  print(): void;
}

interface Loggable {
  log(): void;
}

// T 必须同时实现 Printable 和 Loggable
function process<T extends Printable & Loggable>(item: T): void {
  item.print();
  item.log();
}

泛型接口

// 泛型接口
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

interface PaginatedData<T> {
  items: T[];
  total: number;
  page: number;
}

// 使用泛型接口
interface User {
  id: number;
  name: string;
}

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

const paginated: ApiResponse<PaginatedData<User>> = {
  data: {
    items: [{ id: 1, name: "Alice" }],
    total: 100,
    page: 1
  },
  status: 200,
  message: "OK"
};

泛型类

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

  size(): number {
    return this.items.length;
  }
}

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

const stringStack = new Stack<string>();
stringStack.push("hello");
stringStack.push("world");
console.log(stringStack.pop()); // "world"

泛型类约束

class SortedList<T extends { compareTo(other: T): number }> {
  private items: T[] = [];

  add(item: T): void {
    const index = this.items.findIndex(
      existing => item.compareTo(existing) < 0
    );
    if (index === -1) {
      this.items.push(item);
    } else {
      this.items.splice(index, 0, item);
    }
  }

  getAll(): T[] {
    return [...this.items];
  }
}

泛型默认值

// 给泛型参数设置默认值
interface Container<T = string> {
  value: T;
}

const strContainer: Container = { value: "hello" };     // 默认 string
const numContainer: Container<number> = { value: 42 };  // 指定 number

// 函数泛型默认值
function createState<T = string>(initial: T): [T, (value: T) => void] {
  let state = initial;
  return [state, (value: T) => { state = value; }];
}

常见泛型模式

工厂模式

interface Factory<T> {
  create(): T;
}

class UserFactory implements Factory<User> {
  create(): User {
    return { id: 0, name: "", email: "" };
  }
}

// 泛型工厂
function createInstance<T>(type: new () => T): T {
  return new type();
}

缓存模式

class Cache<K, V> {
  private store = new Map<K, { value: V; expiry: number }>();

  set(key: K, value: V, ttl: number = 60000): void {
    this.store.set(key, {
      value,
      expiry: Date.now() + ttl
    });
  }

  get(key: K): V | undefined {
    const entry = this.store.get(key);
    if (!entry) return undefined;
    if (Date.now() > entry.expiry) {
      this.store.delete(key);
      return undefined;
    }
    return entry.value;
  }
}

const userCache = new Cache<number, User>();
userCache.set(1, { id: 1, name: "Alice", email: "[email protected]" });

分组函数

function groupBy<T, K extends string | number>(
  items: T[],
  keyFn: (item: T) => K
): Record<K, T[]> {
  return items.reduce((groups, item) => {
    const key = keyFn(item);
    if (!groups[key]) {
      groups[key] = [];
    }
    groups[key].push(item);
    return groups;
  }, {} as Record<K, T[]>);
}

const users = [
  { name: "Alice", role: "admin" as const },
  { name: "Bob", role: "user" as const },
  { name: "Charlie", role: "admin" as const }
];

const grouped = groupBy(users, u => u.role);
// { admin: [...], user: [...] }

泛型工具类型

TypeScript 内置了许多泛型工具类型:

// Partial - 所有属性变可选
type PartialUser = Partial<User>;

// Required - 所有属性变必选
type RequiredUser = Required<PartialUser>;

// Pick - 选取部分属性
type UserBasic = Pick<User, "id" | "name">;

// Omit - 排除部分属性
type UserWithoutId = Omit<User, "id">;

// Record - 构造键值对类型
type UserMap = Record<string, User>;

// Exclude - 从联合类型中排除
type Status = "active" | "inactive" | "banned";
type ActiveStatus = Exclude<Status, "banned">; // "active" | "inactive"

// Extract - 从联合类型中提取
type BannedStatus = Extract<Status, "banned">; // "banned"

// ReturnType - 获取函数返回值类型
function getUser() { return { id: 1, name: "Alice" }; }
type UserReturn = ReturnType<typeof getUser>;

业务场景:类型安全的事件系统

interface EventMap {
  login: { userId: string; timestamp: number };
  logout: { userId: string };
  purchase: { orderId: string; amount: number };
  error: { code: number; message: string };
}

class TypedEventEmitter<Events extends Record<string, any>> {
  private handlers = new Map<string, Set<Function>>();

  on<K extends keyof Events>(
    event: K,
    handler: (data: Events[K]) => void
  ): void {
    if (!this.handlers.has(event as string)) {
      this.handlers.set(event as string, new Set());
    }
    this.handlers.get(event as string)!.add(handler);
  }

  off<K extends keyof Events>(
    event: K,
    handler: (data: Events[K]) => void
  ): void {
    this.handlers.get(event as string)?.delete(handler);
  }

  emit<K extends keyof Events>(event: K, data: Events[K]): void {
    this.handlers.get(event as string)?.forEach(handler => handler(data));
  }
}

const emitter = new TypedEventEmitter<EventMap>();

// ✅ 类型安全的事件监听
emitter.on("login", (data) => {
  console.log(data.userId);     // ✅ string
  console.log(data.timestamp);  // ✅ number
});

emitter.on("purchase", (data) => {
  console.log(data.amount);     // ✅ number
});

// ❌ 类型错误
// emitter.on("login", (data) => {
//   console.log(data.orderId); // ❌ 属性不存在
// });

注意事项

  1. 泛型参数名称约定:单字母用 T(Type)、UV;有含义时用完整名称如 TItemTResponse
  2. 不要过度使用泛型——如果不需要复用,直接使用具体类型
  3. 善用类型推断——不需要每次都显式指定泛型参数
  4. 约束泛型参数——使用 extends 确保泛型满足最低要求
  5. 泛型默认值——可以减少使用时需要指定的参数数量

扩展阅读