第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 | undefined2.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> 工具类型,递归地将所有属性变为只读。