TypeScript的泛型(Generics)是一种在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。泛型的主要目的是提高代码的复用性,让你可以编写灵活且可重用的组件。
常规类型
不用泛型的时候:
// number
function identity(arg: number): number {
return arg;
}
// any
function identity(arg: any): any {
return arg;
}
使用any类型会导致这个函数可以接收任何类型的arg参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。
泛型类型
泛型在TypeScript中主要通过来定义,其中T是一个类型变量,代表一个待指定的类型。你可以在函数、接口或类的定义中使用这个类型变量,然后在具体使用的时候通过传入的类型参数来指定这个类型变量的实际类型。
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
let output = myIdentity<string>("Hello world"); // 类型参数是 string
console.log(output); // 输出 "Hello world"
let output2 = myIdentity<number>(42); // 类型参数是 number
console.log(output2); // 输出 42
在这个例子中,我们定义了一个泛型函数identity,它接受一个类型为T的参数并返回同类型的值。然后我们可以创建这个函数的引用myIdentity,并传入不同的类型参数(如string和number)来调用它。
泛型类
泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
GenericNumber类的使用是十分直观的,并且你可能已经注意到了,没有什么去限制它只能使用number类型。 也可以使用字符串或其它更复杂的类型。
泛型约束
当想访问参数的属性的时候,编译器并不能证明每种类型都有指定的属性。
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
此时我们可以定义一个接口进行约束条件。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
现在这个泛型函数被定义了约束,因此它不再是适用于任意类型
loggingIdentity(3); // Error, number doesn't have a .length property
loggingIdentity({length: 10, value: 3}); // 正确
在泛型约束中使用类型参数
可以声明一个类型参数,且它被另一个类型参数所约束。 比如,现在我们想要用属性名从对象里获取这个属性。 并且我们想要确保这个属性存在于对象 obj上,因此我们需要在这两个类型之间使用约束。
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
在泛型里使用类类型
使用泛型创建工厂函数时,需要引用构造函数的类类型。
function create<T>(c: {new(): T; }): T {
return new c();
}
使用原型属性推断并约束构造函数与类实例的关系。
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!