1.泛型约束
在 TypeScript 中,我们可以对泛型进行约束,使得泛型只能接受某些类型或者符合某些条件的类型。这样可以增强泛型的灵活性和安全性。
泛型约束有两种形式:类型约束和属性约束。
类型约束是指使用 extends 关键字来约束泛型的类型,例如:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
在上面的示例中,使用 extends Lengthwise
来限制泛型参数 T 必须符合 Lengthwise 接口的定义,也就是必须包含 length 属性。
属性约束是指使用类型参数中的属性来约束其他属性,例如:
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
在上面的示例中,使用 <T, K extends keyof T>
来定义泛型,其中 K 继承自 T 的 key,即 K 只能取 T 中定义过的属性名。
这样,在使用 getProperty
函数时,就可以保证 key 参数只能传入 T 中定义过的属性名。
2.在泛型约束中使用类型参数
在泛型约束中使用类型参数通常称为 F-bounded polymorphism 或递归类型限定。这是指在泛型类型定义中使用类型参数来定义其自身的约束条件。
一个常见的例子是要求泛型类型必须实现某个接口或继承某个类。在这种情况下,我们需要将泛型类型的类型参数约束为该接口或类,同时在定义接口或类的时候也要使用该类型参数。
下面是一个例子:
interface Shape {
area(): number;
}
class Rectangle implements Shape {
constructor(private width: number, private height: number) {}
area() {
return this.width * this.height;
}
}
class Circle implements Shape {
constructor(private radius: number) {}
area() {
return Math.PI * this.radius ** 2;
}
}
function compareShapes<T extends Shape>(shape1: T, shape2: T) {
return shape1.area() - shape2.area();
}
const rect1 = new Rectangle(10, 20);
const rect2 = new Rectangle(5, 15);
const circle1 = new Circle(10);
console.log(compareShapes(rect1, rect2)); // 50
console.log(compareShapes(rect1, circle1)); // Error: Argument of type 'Circle' is not assignable to parameter of type 'Rectangle'.
在这个例子中,我们定义了一个 Shape
接口,它定义了一个 area()
方法。然后,我们定义了 Rectangle
和 Circle
类来实现 Shape
接口,并实现了它们各自的 area()
方法。
接下来,我们定义了一个泛型函数 compareShapes
,它接受两个实现了 Shape
接口的对象,并比较它们的面积。我们使用类型参数 T
来约束传递给该函数的对象类型必须是实现了 Shape
接口的类型。因此,我们可以将 Rectangle
和 Circle
对象传递给该函数,并对它们进行比较。
注意,我们必须将类型参数 T
约束为 Shape
接口,这样才能保证传递给该函数的对象实现了该接口。同时,我们在 compareShapes
函数的参数中使用了该类型参数,以确保两个传递的对象类型相同。
3.在泛型中使用类类型
在泛型中使用类类型可以通过在泛型约束中使用 new()
关键字来实现。这个约束表示泛型参数必须是一个可以被实例化的构造函数。例如:
interface IName {
name: string;
}
class Person implements IName {
constructor(public name: string) {}
}
function createInstance<T extends IName>(c: new() => T): T {
return new c();
}
const person = createInstance(Person);
person.name = "John Doe";
console.log(person); // Output: Person { name: "John Doe" }
在上面的例子中,我们定义了一个接口 IName
,它有一个属性 name
。我们还定义了一个类 Person
,它实现了 IName
接口。然后,我们定义了一个泛型函数 createInstance
,它接受一个泛型参数 T
,它必须是一个具有 name
属性的类型。这个泛型函数也接受一个构造函数作为参数,该构造函数必须可以实例化一个 T
类型的对象。
在函数内部,我们使用 new c()
实例化一个 T
类型的对象并将其返回。最后,我们使用 createInstance
函数来实例化一个 Person
对象,并为其属性 name
赋值为 "John Doe"。