在 TypeScript 中,泛型约束(Generic Constraints)用于限制泛型可以接受的类型范围,确保泛型参数不是只能接受任意类型,而是只能接受满足特定条件的类型。这既保留了泛型的灵活性,又增强了类型安全性。
为什么需要泛型约束?
例如,尝试访问泛型参数的 length
属性:
// 错误示例:TypeScript 无法确定 T 有 length 属性 function getLength<T>(arg: T): number {return arg.length; // 报错:Property 'length' does not exist on type 'T' }
这时就需要泛型约束来指定 T
必须包含 length
属性。
如何使用泛型约束?
通过 extends
关键字可以实现泛型约束,语法为 <T extends 约束类型>
。
1. 基础约束:限制为特定结构
最常见的约束是要求泛型必须包含某些属性或方法。例如,约束 T
必须有 length
属性:
// 定义一个接口作为约束条件 interface HasLength {length: number; }// 使用 extends 约束 T 必须符合 HasLength 结构 function getLength<T extends HasLength>(arg: T): number {return arg.length; // 现在可以安全访问 length 了 }// 正确用法:字符串、数组等有 length 属性的类型 getLength("hello"); // 5(字符串有 length) getLength([1, 2, 3]); // 3(数组有 length)// 错误用法:数字没有 length 属性 getLength(123); // 报错:Argument of type 'number' is not assignable to parameter of type 'HasLength'
2. 约束为另一个类型的子类型
可以约束泛型必须是另一个类型的子类型,例如约束 T
必须是 User
类型的子类型:
interface User {id: number;name: string; }// 约束 T 必须是 User 的子类型(即必须包含 id 和 name) function getUserInfo<T extends User>(user: T): string {return `ID: ${user.id}, Name: ${user.name}`; }// 正确:符合 User 结构 getUserInfo({ id: 1, name: "Alice" });// 正确:扩展了 User 结构(允许额外属性) getUserInfo({ id: 2, name: "Bob", age: 30 });// 错误:缺少 name 属性,不符合 User 结构 getUserInfo({ id: 3 }); // 报错:Property 'name' is missing
3. 约束为 keyof 另一个类型(键约束)
使用 keyof
可以约束泛型必须是某个对象类型的键,常用于安全地访问对象属性:
// 约束 K 必须是 T 的键(keyof T 返回 T 所有键的联合类型) function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {return obj[key]; // 安全访问 obj 的属性 }const person = { name: "Alice", age: 25 };// 正确:key 是 person 的有效键 getProperty(person, "name"); // "Alice"(类型:string) getProperty(person, "age"); // 25(类型:number)// 错误:key 不是 person 的键 getProperty(person, "height"); // 报错:Argument of type '"height"' is not assignable to parameter of type '"name" | "age"'
4. 多个约束(交叉类型)
如果需要同时满足多个约束,可以使用交叉类型(&
)组合多个条件:
interface HasLength {length: number; }interface HasName {name: string; }// 约束 T 必须同时满足 HasLength 和 HasName function getInfo<T extends HasLength & HasName>(obj: T): string {return `Name: ${obj.name}, Length: ${obj.length}`; }// 正确:同时有 name 和 length getInfo({ name: "Test", length: 10 });// 错误:缺少 length getInfo({ name: "Test" }); // 报错:Property 'length' is missing
总结
1.约束为包含特定属性 / 方法的结构
2.约束为另一个类型的子类型
3.结合 keyof
约束为对象的键
4.多个约束的组合(交叉类型)