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: "a@b.com" });
分组函数
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); // ❌ 属性不存在
// });
注意事项
- 泛型参数名称约定:单字母用
T(Type)、U、V;有含义时用完整名称如TItem、TResponse - 不要过度使用泛型——如果不需要复用,直接使用具体类型
- 善用类型推断——不需要每次都显式指定泛型参数
- 约束泛型参数——使用
extends确保泛型满足最低要求 - 泛型默认值——可以减少使用时需要指定的参数数量