泛型约束和泛型默认参数有什么作用?
简化版
泛型约束用 extends 限制类型参数必须满足某种结构,让函数内部可以安全访问特定属性。泛型默认参数用于调用方不传类型参数时提供默认类型。
它们都用于提高泛型 API 的可用性:约束保证安全,默认参数减少使用成本。
详细版
function getId<T extends { id: string }>(value: T) {
return value.id;
}
没有约束时,T 可以是任何类型,不能直接访问 id。
默认参数:
type ApiResponse<T = unknown> = {
data: T;
code: number;
};
type AnyResponse = ApiResponse;
默认参数常用于组件 props、请求响应、工具类型,让简单场景少写类型参数,复杂场景仍可覆盖。
完整版教学
一、泛型不是越宽越好
完全自由的 T 表示任何类型。
function read<T>(value: T) {
// value.id; // 不能访问
}
如果函数逻辑需要 id,就要把这个要求写进约束。
泛型约束是在告诉编译器:我不是要任何 T,我要满足某些能力的 T。
二、extends 表示结构约束
function pickId<T extends { id: string | number }>(item: T) {
return item.id;
}
只要传入对象有兼容的 id 字段即可。
pickId({ id: 1, name: "Ada" });
这体现了 TypeScript 的结构化类型系统。
三、keyof 约束常用于安全取值
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
K extends keyof T 保证 key 一定是 obj 的键。
const user = { id: 1, name: "Ada" };
const name = get(user, "name");
四、默认参数降低使用成本
type Result<T = unknown, E = Error> = {
data?: T;
error?: E;
};
简单场景:
type R1 = Result;
复杂场景:
type R2 = Result<User, ApiError>;
五、默认参数可以依赖前面的参数
type Box<T, U = T[]> = {
value: T;
list: U;
};
泛型默认参数从左到右解析,后面的默认值可以引用前面的类型参数。
| 写法 | 含义 |
|---|---|
Box<string> | U 默认为 string[] |
Box<string, Set<string>> | 手动指定 U |
六、约束不是转换
function f<T extends string | number>(value: T) {
return value;
}
extends 只是限制传入类型范围,不会把值转换成 string 或 number。运行时仍然是原值。
如果需要运行时转换,必须写真实代码。
七、常见误区与追问
- 误区:泛型约束会改变运行时数据。 它只影响类型检查,不做转换。
- 误区:
T extends { id: string }要求对象只能有 id。 结构兼容允许额外属性。 - 误区:默认泛型参数会降低类型安全。 合理默认能减少样板,同时保留覆盖能力。
- 误区:所有泛型都应该有默认值。 关键类型参数若必须显式表达,就不应随便默认。
- 追问:
K extends keyof T有什么用? 保证 key 是对象真实键,并返回对应属性类型。 - 追问:默认类型参数能引用其他参数吗? 后面的默认参数可以引用前面的类型参数。
八、加强记忆
记忆句:
constraint -> what T must have
default -> what T becomes if omitted
回答时用 get<T, K extends keyof T> 说明约束价值,再用 ApiResponse<T = unknown> 说明默认参数的 API 友好性。