第6章:泛型
泛型约束
什么是泛型约束?
泛型约束(Generic Constraints)允许我们限制泛型类型参数的范围,确保它们满足特定条件。通过使用 extends 关键字,我们可以要求泛型类型必须符合某种结构或继承自某个基类。
基本语法
function example<T extends ConstraintType>(arg: T): T {
// 函数体
}
其中:
T是泛型类型参数。ConstraintType是约束条件,可以是接口、类或字面量类型。
典型应用场景
1. 确保属性存在
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): void {
console.log(arg.length);
}
logLength("hello"); // 5
logLength([1, 2, 3]); // 3
logLength(42); // 错误:number 没有 length 属性
2. 结合 keyof 约束对象键
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const person = { name: "Alice", age: 30 };
getProperty(person, "name"); // "Alice"
getProperty(person, "gender"); // 错误:gender 不是 person 的键
多重约束
TypeScript 支持通过交叉类型实现多重约束:
interface Printable {
print(): void;
}
interface Loggable {
log(): void;
}
function process<T extends Printable & Loggable>(item: T) {
item.print();
item.log();
}
常见问题与解决方案
问题1:过度约束
过度使用约束可能导致代码灵活性降低。建议:
- 优先使用最小约束原则
- 考虑使用类型守卫替代约束
问题2:循环约束
避免自引用的约束定义:
// 错误示例
interface Invalid<T extends Invalid<T>> {}
最佳实践
- 明确约束意图:约束应该清晰表达业务需求
- 文档化复杂约束:使用 JSDoc 说明约束条件
- 优先选择接口约束:比类约束更灵活
- 测试边界情况:验证约束在极端情况下的行为
示例:电商系统中的应用
interface Product {
id: string;
price: number;
}
class Cart<T extends Product> {
private items: T[] = [];
addItem(item: T) {
this.items.push(item);
}
getTotal(): number {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}
思考题:如何设计一个泛型函数,要求参数必须是包含
id: string的数组,且返回按 id 排序后的数组?
通过泛型约束,我们可以在保持类型安全的同时,实现高度可复用的代码逻辑。
