声明:本学习系列参考了TypeScript3.3英文版官网教程
泛型(Generics)
在typescript中我们可以通过使用泛型来定义通用的可重复使用的组件,而不是只局限于一种具体的类型。
1、Hello World
function identity<T>(arg: T): T {
return arg;
}
在这里我们使用type
变量去定义一个函数,它将会返回和传入类型相同的值。
我们可以通过两种方式来调用它
第一种显示声明
let output = identity<string>("myString"); // type of output will be 'string'
第二种使用类型推断
let output = identity("myString"); // type of output will be 'string'
当你使用泛型参数的时候,编译器认为你的泛型参数可以指定任意类型的值,如果使用了不存在的属性将会报错,如下例,当传入number
类型的值时,由于number没有length
属性将会出错,所以编译器认为这个泛型的定义是有问题的。
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
由于不是任何类型都有length
属性,要修复这个问题,我们可以传入T
类型的数组
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
也可以使用以下方式来创建泛型数组
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
2、泛型类型
上面我们了解来如何定义一个泛型函数,那么如何来定义一个泛型类型或泛型接口呢?
定义泛型函数类型
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
也可以使用不同泛型类型参数
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <U>(arg: U) => U = identity;
也可以使用对象字面量签名的方式来定义一个泛型类型
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: {<T>(arg: T): T} = identity;
这就引导我们去定义一个泛型接口
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
如果我们想让我们的type
参数在整个接口内都可用,可以把它提到外面
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
3、泛型类
除了泛型接口,我们也可以定义泛型类,但是不能够去定义一个泛型enums
或者namespaces
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; };
正如我们前面讨论的,类包括实例部分和静态部分,泛型只能定义类的实例部分而不能定义类的静态部分。
4、泛型约束
4.1、通用
在前面的例子中我们讨论了并不是所有的类型都有length
属性,但是如果某个类型具有length
属性那么我们就应该允许它,要做到这一步,我们需要使用extends
添加一些约束。
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;
}
添加了约束之后,该泛型就不再适用所有的类型了,只有具有length
属性的类型才可以传入。
loggingIdentity(3); // Error, number doesn't have a .length property
loggingIdentity({length: 10, value: 3});
4.2、在泛型约束中使用type参数
一个type参数可以被另一个type参数约束,如下例该函数不能返回T中不存在的属性值
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'.
4.3、在泛型中使用class类型
当我们使用泛型创建一个工厂函数时,它是需要去指定构造器的类型的,例如
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!