TypeScript 泛型
大约 11 分钟约 3199 字
TypeScript 泛型
简介
TypeScript 泛型允许在定义函数、接口、类和类型别名时使用类型参数,实现类型安全的代码复用。泛型是 TypeScript 类型系统的基石之一,几乎所有框架和库的核心 API 都大量使用泛型。理解泛型约束(extends)、默认类型参数、条件分发和常见设计模式,是编写高质量 TypeScript 代码的关键。泛型的核心思想是"参数化类型"——将类型作为参数传递,让一套代码适配多种类型。
特点
实现
泛型函数与接口
// ========== 基础泛型函数 ==========
// 类型参数 T 在调用时推断或显式指定
function identity<T>(value: T): T {
return value;
}
const num = identity(42); // T 推断为 number
const str = identity('hello'); // T 推断为 string
const bool = identity<boolean>(true); // 显式指定 T 为 boolean
// 多个类型参数
function pair<A, B>(first: A, second: B): [A, B] {
return [first, second];
}
const p = pair(1, 'hello'); // [number, string]
// 泛型与约束 — extends 限制类型参数必须满足的条件
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: '张三', age: 25, email: 'z@example.com' };
const name = getProperty(user, 'name'); // string — 推断正确
const age = getProperty(user, 'age'); // number — 推断正确
// getProperty(user, 'phone'); // Error — 'phone' 不存在于 user
// 泛型与默认值
function createList<T = string>(...items: T[]): T[] {
return items;
}
const names = createList('a', 'b'); // string[]
const nums = createList<number>(1, 2); // number[]
// ========== 泛型接口 ==========
// API 响应封装
interface ApiResponse<T> {
code: number;
message: string;
data: T;
timestamp: number;
requestId: string;
}
interface PaginatedResponse<T> extends ApiResponse<T[]> {
total: number;
page: number;
pageSize: number;
totalPages: number;
}
// 使用
interface User {
id: number;
name: string;
email: string;
}
interface Order {
id: number;
total: number;
status: string;
}
const userRes: ApiResponse<User> = {
code: 200,
message: 'success',
data: { id: 1, name: '张三', email: 'z@example.com' },
timestamp: Date.now(),
requestId: 'req-123',
};
const listRes: PaginatedResponse<User> = {
code: 200,
message: 'success',
data: [{ id: 1, name: '张三', email: 'z@example.com' }],
total: 100,
page: 1,
pageSize: 20,
totalPages: 5,
timestamp: Date.now(),
requestId: 'req-124',
};
// 泛型接口作为函数类型
interface Fetcher<T> {
(url: string, options?: RequestInit): Promise<ApiResponse<T>>;
}
const fetchUser: Fetcher<User> = async (url) => {
const res = await fetch(url);
return res.json();
};泛型类与约束
// ========== 泛型类 — 通用仓储 ==========
class Repository<T extends { id: string | number }> {
private items = new Map<string | number, T>();
add(item: T): void {
this.items.set(item.id, item);
}
get(id: string | number): T | undefined {
return this.items.get(id);
}
getAll(): T[] {
return [...this.items.values()];
}
update(id: string | number, updates: Partial<T>): T | undefined {
const item = this.items.get(id);
if (!item) return undefined;
this.items.set(id, { ...item, ...updates });
return this.items.get(id);
}
remove(id: string | number): boolean {
return this.items.delete(id);
}
find(predicate: (item: T) => boolean): T | undefined {
return this.getAll().find(predicate);
}
filter(predicate: (item: T) => boolean): T[] {
return this.getAll().filter(predicate);
}
count(): number {
return this.items.size;
}
exists(id: string | number): boolean {
return this.items.has(id);
}
}
// 使用
interface Device {
id: string;
name: string;
status: 'online' | 'offline';
type: string;
}
const deviceRepo = new Repository<Device>();
deviceRepo.add({ id: 'PLC-001', name: '主控PLC', status: 'online', type: 'PLC' });
deviceRepo.add({ id: 'SENSOR-001', name: '温度传感器', status: 'online', type: 'Sensor' });
const onlineDevices = deviceRepo.filter(d => d.status === 'online');
// ========== 泛型工厂 ==========
function createFactory<T>(constructor: new (...args: any[]) => T) {
return {
create(...args: any[]): T {
return new constructor(...args);
},
createBatch(items: any[]): T[] {
return items.map(item => new constructor(item));
},
};
}
// ========== 泛型约束实战 ==========
// 约束 T 必须有 length 属性
function logLength<T extends { length: number }>(item: T): number {
console.log(`长度: ${item.length}`);
return item.length;
}
logLength('hello'); // 5
logLength([1, 2, 3]); // 3
logLength({ length: 10 }); // 10
// logLength(123); // Error — number 没有 length 属性
// 约束 T 必须是可迭代的
function first<T extends Iterable<any>>(iterable: T): T extends Iterable<infer U> ? U | undefined : undefined {
for (const item of iterable) return item;
return undefined;
}
first([1, 2, 3]); // 1
first('hello'); // 'h'
// 约束 T 必须实现特定接口
interface Disposable {
dispose(): void;
}
function withDisposable<T extends Disposable>(resource: T, action: (r: T) => void): void {
try {
action(resource);
} finally {
resource.dispose();
}
}工具类型实战
// ========== 类型安全的事件系统 ==========
type EventMap = {
login: { userId: string; timestamp: number };
logout: { userId: string };
error: { message: string; code: number };
'user:update': { id: number; changes: Record<string, any> };
};
class TypedEventEmitter<Events extends Record<string, any>> {
private handlers = new Map<keyof Events, Set<Function>>();
on<E extends keyof Events>(event: E, handler: (data: Events[E]) => void): () => void {
if (!this.handlers.has(event)) {
this.handlers.set(event, new Set());
}
this.handlers.get(event)!.add(handler);
// 返回取消订阅函数
return () => this.handlers.get(event)?.delete(handler);
}
once<E extends keyof Events>(event: E, handler: (data: Events[E]) => void): () => void {
const unsubscribe = this.on(event, (data) => {
handler(data);
unsubscribe();
});
return unsubscribe;
}
emit<E extends keyof Events>(event: E, data: Events[E]): void {
this.handlers.get(event)?.forEach(h => h(data));
}
off<E extends keyof Events>(event: E, handler: (data: Events[E]) => void): void {
this.handlers.get(event)?.delete(handler);
}
removeAllListeners(event?: keyof Events): void {
if (event) {
this.handlers.delete(event);
} else {
this.handlers.clear();
}
}
}
// 使用 — 完全类型安全
const emitter = new TypedEventEmitter<EventMap>();
// on 和 emit 的事件名和参数类型必须匹配
emitter.on('login', (data) => {
console.log(data.userId); // string — 自动推断
console.log(data.timestamp); // number — 自动推断
});
emitter.emit('login', { userId: '123', timestamp: Date.now() });
// emitter.emit('login', { userId: 123 }); // Error — userId 应为 string
// emitter.on('nonexistent', () => {}); // Error — 不存在的事件名
// ========== 泛型 API 请求封装 ==========
async function apiRequest<T>(
url: string,
options?: RequestInit
): Promise<ApiResponse<T>> {
const res = await fetch(url, {
headers: { 'Content-Type': 'application/json', ...options?.headers },
...options,
});
const data = await res.json();
if (!res.ok) {
throw new ApiError(data.code, data.message, res.status);
}
return data as ApiResponse<T>;
}
class ApiError extends Error {
constructor(
public code: string,
message: string,
public status: number
) {
super(message);
}
}
// 使用
const users = await apiRequest<User[]>('/api/users');
const user = await apiRequest<User>('/api/users/1');
const orders = await apiRequest<PaginatedResponse<Order>>('/api/orders?page=1');条件泛型与分发
// ========== 条件分发 ==========
// 当 T 是联合类型时,条件类型会自动分发
type ToArrayNonDist<T> = [T] extends [any] ? T[] : T[];
type R1 = ToArrayNonDist<string | number>; // (string | number)[] — 不分发
type ToArrayDist<T> = T extends any ? T[] : T[];
type R2 = ToArrayDist<string | number>; // string[] | number[] — 分发
// ========== 提取函数返回值类型 ==========
type MyReturnType<T extends (...args: any[]) => any> =
T extends (...args: any[]) => infer R ? R : never;
type Fn = (x: number) => string;
type R3 = MyReturnType<Fn>; // string
// ========== 提取 Promise 内部类型 ==========
type UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T;
type R4 = UnwrapPromise<Promise<string>>; // string
type R5 = UnwrapPromise<Promise<Promise<number>>>; // number
// ========== 函数重载与泛型 ==========
// 使用泛型替代多个重载
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
const merged = merge({ name: '张三' }, { age: 25 });
// { name: string; age: number }
// ========== 泛型与映射类型 ==========
// 将对象的所有方法变为异步版本
type Asyncify<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => infer R
? (...args: Parameters<T[K]>) => Promise<R>
: T[K];
};
interface SyncApi {
getUser(id: number): User;
createOrder(data: Order): Order;
version: string;
}
type AsyncApi = Asyncify<SyncApi>;
// {
// getUser(id: number): Promise<User>;
// createOrder(data: Order): Promise<Order>;
// version: string;
// }泛型工具类型进阶
// ========== 深度 Partial ==========
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object
? DeepPartial<T[K]>
: T[K];
};
interface Config {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
cache: {
enabled: boolean;
ttl: number;
};
}
type PartialConfig = DeepPartial<Config>;
// 所有层级都是可选的
const config: PartialConfig = {
database: {
credentials: { username: 'admin' }
}
};
// ========== 深度 Readonly ==========
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
// ========== 提取构造函数参数类型 ==========
type ConstructorParameters<T extends new (...args: any[]) => any> =
T extends new (...args: infer P) => any ? P : never;
class MyClass {
constructor(public name: string, public age: number) {}
}
type MyClassParams = ConstructorParameters<typeof MyClass>; // [string, number]
// ========== 路由参数类型推导 ==========
// 模拟前端路由的参数类型安全
type RouteParams<Path extends string> =
Path extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param | keyof RouteParams<Rest>]: string }
: Path extends `${string}:${infer Param}`
? { [K in Param]: string }
: {};
type UserRoute = '/users/:userId';
type UserParams = RouteParams<UserRoute>; // { userId: string }
type PostRoute = '/users/:userId/posts/:postId';
type PostParams = RouteParams<PostRoute>; // { userId: string; postId: string }
// 类型安全的路由函数
function navigate<Path extends string>(
path: Path,
params: RouteParams<Path>
): void {
let resolved = path as string;
for (const [key, value] of Object.entries(params)) {
resolved = resolved.replace(`:${key}`, value as string);
}
console.log(resolved);
}
navigate('/users/:userId', { userId: '123' }); // OK
// navigate('/users/:userId', {}); // Error — 缺少 userId
// ========== 递归类型 — 树形数据 ==========
interface TreeNode<T> {
data: T;
children?: TreeNode<T>[];
}
function flattenTree<T>(node: TreeNode<T>): T[] {
const result: T[] = [node.data];
if (node.children) {
for (const child of node.children) {
result.push(...flattenTree(child));
}
}
return result;
}
// 使用
const orgTree: TreeNode<{ id: string; name: string }> = {
data: { id: '1', name: 'CEO' },
children: [
{
data: { id: '2', name: 'CTO' },
children: [
{ data: { id: '4', name: '前端组长' } },
{ data: { id: '5', name: '后端组长' } },
],
},
{ data: { id: '3', name: 'CFO' } },
],
};
console.log(flattenTree(orgTree));
// [{ id: '1', name: 'CEO' }, { id: '2', name: 'CTO' }, ...]优点
缺点
总结
泛型是 TypeScript 类型系统的核心。通过泛型函数、接口和类实现类型安全的代码复用。extends 约束类型参数必须满足的条件,keyof 获取类型的所有属性名联合类型。泛型默认值(T = string)在不指定时使用默认类型。条件泛型配合 infer 可以从复杂类型中提取子类型。建议在工具函数、数据结构和框架级代码中使用泛型,业务代码保持适度。
关键知识点
- 泛型参数在调用时推断或显式指定。
- extends 约束泛型参数必须满足的条件。
- keyof 获取类型的所有属性名联合类型。
- 泛型默认值(T = string)在不指定时使用。
- 条件泛型在联合类型上自动分发。
- infer 在条件类型中推断类型的一部分。
项目落地视角
- 为 API 请求封装泛型函数确保响应类型安全。
- 数据结构(Map、Repository、EventEmitter)使用泛型支持多种类型。
- 避免过度使用泛型,简单场景直接使用具体类型。
- 建立项目级泛型命名规范(TData、TResult、TKey)。
常见误区
- 用 any 作为泛型约束失去类型安全。
- 泛型参数过多(>3)导致代码难以理解。
- 忘记在调用时指定泛型参数导致推断为 unknown。
- 泛型约束过于严格导致无法传入合法类型。
进阶路线
- 学习条件类型和映射类型的高级泛型技巧。
- 研究 TypeScript 内置工具类型的实现原理。
- 尝试 type-challenges 练习泛型编程。
- 学习 satisfies 运算符与泛型的配合。
适用场景
- 工具函数和库的 API 设计。
- 通用数据结构(Repository、Cache、EventEmitter)。
- API 请求和响应的类型定义。
- React/Vue 组件 Props 和 State 的类型约束。
落地建议
- 为项目定义标准泛型命名规范(TData、TResult、TKey)。
- 封装项目级工具类型库。
- 使用泛型约束而非 any 保证类型安全。
- 复杂泛型添加注释说明设计意图。
排错清单
- 检查泛型约束是否正确限制类型范围。
- 确认类型推断结果是否匹配预期(使用 hover 查看)。
- 使用 TS Playground 验证复杂泛型逻辑。
- 检查泛型默认值是否合理。
泛型性能与编译优化
减少泛型编译开销
// ========== 避免过度复杂的泛型推断 ==========
// Bad: 编译器需要遍历大量类型组合
type ComplexInference<T extends Record<string, any>, K extends keyof T> =
T[K] extends Array<infer U>
? U extends object
? { [P in keyof U]: U[P] extends Function ? never : U[P] }
: U
: T[K];
// Good: 拆分为多个简单的类型别名
type UnwrapArray<T> = T extends Array<infer U> ? U : T;
type FilterMethods<T> = { [K in keyof T as T[K] extends Function ? never : K]: T[K] };
type CleanResult<T> = T extends object ? FilterMethods<T> : T;
// ========== 使用 satisfies 替代类型断言 ==========
// Bad: 类型断言绕过检查
const config = {
apiUrl: '/api',
timeout: 5000,
} as Config;
// Good: satisfies 确保类型匹配同时保留字面量类型
const config2 = {
apiUrl: '/api',
timeout: 5000,
} satisfies Config;
// config2.apiUrl 仍然是 string 字面量 '/api'泛型与模块化
// ========== 泛型工具类型库 — 项目级复用 ==========
// types/utils.ts
// 深度合并两个类型
type DeepMerge<A, B> = {
[K in keyof A | keyof B]: K extends keyof B
? B[K] extends object
? A[K] extends object
? DeepMerge<A[K], B[K]>
: B[K]
: B[K]
: K extends keyof A
? A[K]
: never;
};
// 提取 Promise 链中的所有值类型
type PromiseValues<T extends any[]> = {
[K in keyof T]: T[K] extends Promise<infer V> ? V : T[K];
};
// 构建精确的事件映射
type EventPayloads<T extends string> = {
[K in T]: K extends 'click' ? { x: number; y: number }
: K extends 'keypress' ? { key: string }
: K extends 'scroll' ? { scrollTop: number }
: never;
};
// 从联合类型中排除 null 和 undefined(比 NonNullable 更明确)
type NotNull<T> = T extends null | undefined ? never : T;
// 将所有可选属性变为必选(与 Required 相反)
type AllOptional<T> = {
[K in keyof T]?: T[K];
};
// 提取数组元素类型
type ArrayElement<T> = T extends (infer E)[] ? E : never;
// 使用示例
type StringArrayElement = ArrayElement<string[]>; // string
type NumberArrayElement = ArrayElement<number[]>; // number复盘问题
- 你的项目中泛型的使用是否提升了代码质量?
- 如何平衡泛型的灵活性和代码可读性?
- 泛型与函数重载在什么场景下各更合适?
- 哪些工具函数可以从泛型中受益?
