5Programming TypeScript

泛型

泛型编程

泛型函数泛型类泛型约束泛型默认值

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第5章:泛型(Generics)

一、导读

1.1 本章学习目标

第5章讲解 TypeScript 的泛型系统——类型系统的"函数"。泛型让你编写可以适用于多种类型的代码,同时保持类型安全。

本章的核心学习目标包括:

  • 掌握泛型函数和泛型类
  • 理解泛型约束(Constraints):extends 关键字
  • 掌握泛型默认值和推断
  • 学会使用泛型构建可复用的工具函数

1.2 为什么本章如此重要

泛型是 TypeScript 类型系统的核心能力。没有泛型,你就只能写 any 或者为每种类型重复代码。有了泛型,Array<T>、Promise<T>、Map<K, V> 这些标准库类型才能工作,你也能编写同样灵活的工具函数。

1.3 核心问题引导

泛型函数和普通函数有什么区别?类型参数是如何推断的?

extends 在泛型约束中的含义是什么?它和继承有什么关系?

什么是泛型推断?编译器在什么情况下无法推断类型参数?

如何使用泛型实现类型安全的函数重载?


二、核心概念详解

2.1 泛型基础

typescript
// 泛型函数
function identity<T>(value: T): T {
  return value;
}

const num = identity(42);        // T 推断为 number
const str = identity("hello");   // T 推断为 string

// 泛型类
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]; }
  get size(): number { return this.items.length; }
}

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

2.2 泛型约束

typescript
// 约束 T 必须有 length 属性
function logLength<T extends { length: number }>(value: T): void {
  console.log(`Length: ${value.length}`);
}

logLength("hello");        // OK,string 有 length
logLength([1, 2, 3]);      // OK,数组有 length
// logLength(42);           // 错误!number 没有 length

// 约束 T 必须有某个属性
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name");    // string
const age = getProperty(user, "age");      // number
// getProperty(user, "email");             // 错误!

2.3 泛型工具类型

typescript
// Partial<T>:所有属性变为可选
type PartialUser = Partial<User>;

// Required<T>:所有属性变为必填
type RequiredUser = Required<Partial<User>>;

// Pick<T, K>:选取部分属性
type UserName = Pick<User, "name">;

// Omit<T, K>:排除部分属性
type UserWithoutAge = Omit<User, "age">;

// Record<K, V>:键值对类型
type StringMap = Record<string, string>;

// Readonly<T>:所有属性变为只读
type ReadonlyUser = Readonly<User>;

// ReturnType<T>:获取函数返回类型
type FnReturn = ReturnType<() => string>;    // string

// Parameters<T>:获取函数参数类型(元组)
type FnParams = Parameters<(a: string, b: number) => void>;    // [string, number]

2.4 条件类型

typescript
// 条件类型:T extends U ? X : Y
type IsString<T> = T extends string ? "yes" : "no";

type A = IsString<string>;    // "yes"
type B = IsString<number>;    // "no"

// infer 关键字:在条件类型中推断类型变量
type ReturnType2<T> = T extends (...args: any[]) => infer R ? R : never;

type C = ReturnType2<() => string>;    // string
type D = ReturnType2<(x: number) => boolean>;    // boolean

// 分布式条件类型
type ToArray<T> = T extends any ? T[] : never;

type E = ToArray<string | number>;    // string[] | number[]

三、实战案例

3.1 案例一:类型安全的 Event Emitter

typescript
type EventMap = Record<string, any>;

class TypedEmitter<T extends EventMap> {
  private listeners = new Map<keyof T, Set<Function>>();

  on<K extends keyof T>(event: K, listener: (data: T[K]) => void): this {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event)!.add(listener);
    return this;
  }

  emit<K extends keyof T>(event: K, data: T[K]): boolean {
    const handlers = this.listeners.get(event);
    if (!handlers) return false;
    handlers.forEach(h => h(data));
    return true;
  }

  off<K extends keyof T>(event: K, listener: (data: T[K]) => void): this {
    this.listeners.get(event)?.delete(listener);
    return this;
  }
}

// 使用
interface AppEvents {
  login: { userId: string; timestamp: Date };
  logout: { userId: string };
  error: { message: string; code: number };
}

const emitter = new TypedEmitter<AppEvents>();
emitter.on("login", ({ userId, timestamp }) => {
  console.log(`${userId} logged in at ${timestamp}`);
});
emitter.emit("login", { userId: "u123", timestamp: new Date() });

3.2 案例二:类型安全的 ORM 查询构建器

typescript
interface TableSchema {
  users: { id: number; name: string; email: string };
  posts: { id: number; title: string; authorId: number };
}

class QueryBuilder<T extends keyof TableSchema> {
  private tableName: T;
  private conditions: string[] = [];
  
  constructor(tableName: T) {
    this.tableName = tableName;
  }
  
  where<K extends keyof TableSchema[T]>(
    column: K,
    operator: "=" | "!=" | ">" | "<",
    value: TableSchema[T][K]
  ): this {
    this.conditions.push(`${String(column)} ${operator} ${JSON.stringify(value)}`);
    return this;
  }
  
  build(): string {
    const where = this.conditions.length > 0
      ? ` WHERE ${this.conditions.join(" AND ")}`
      : "";
    return `SELECT * FROM ${String(this.tableName)}${where}`;
  }
}

const query = new QueryBuilder("users")
  .where("name", "=", "Alice")
  .where("id", ">", 10)
  .build();
// SELECT * FROM users WHERE name = "Alice" AND id > 10

四、本章小结

泛型函数和类:类型参数 T 让代码适用于多种类型。

泛型约束:extends 限制类型参数的范围。

工具类型:Partial、Pick、Omit、Record 等内置工具类型。

条件类型:T extends U ? X : Y 和 infer 实现高级类型操作。


五、思考与练习

泛型推断在什么情况下会失败?如何手动指定类型参数?

实现一个类型安全的 groupBy 函数。

实现一个 DeepReadonly<T> 工具类型,递归地将所有属性变为只读。


5/7泛型
0%