声明:本学习系列参考了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!