Typescript之泛型(Generics)

146 阅读3分钟

概念

泛型是对共性的提取,允许类型作为另一个类型的参数,从而达到复用的目的。

泛型函数

当我们需要定义一个函数,传入什么类型返回就是什么类型。似乎没有什么方案是可以达到这个目的的,如下:

function identity(arg: number): number {
  return arg;
}

function identity(arg: any): any {
  return arg;
}

第一种写法显然只支持number类型,而第二种写法虽然传入参数可以是任何类型的,但是缺无法约束返回值的类型与入参的类型保持一致。

也许你会想到函数重载,但这显然并不是一个合适的方案;首先代码冗余,其次类型没有覆盖全就需要手动添加重载,如下:

function identity(arg: string): string
function identity(arg: number): number
function identity(arg: boolean): boolean
function identity(arg: any): any {
  return arg;
}

如果我们能够捕获入参的类型,那么我们就能达到约束返回值类型的目的了,而这就需要用到泛型,如下:

function identity<Type>(arg: Type): Type {
  return arg;
}

在函数名称右边使用尖括号包裹的就是类型参数,我们可以在调用函数的时候使用同样的方式来约束入参和返回值的类型,如下:

let output = identity<string>(0); // Argument of type 'number' is not assignable to parameter of type 'string'.

let output = identity<string>(''); // correct !

泛型还可以自动根据入参的类型推导出返回值的类型,如下:

let output = identity('');

output = 1 // Type 'number' is not assignable to type 'string'.

使用了泛型之后参数arg代表就是所有类型,因此对于属性的访问只能访问所有类型共有的属性,如下代码typescript就会抛出警告,因为并不是所有类型都拥有length属性的

function loggingIdentity<Type>(arg: Type): Type {
  console.log(arg.length); // Property 'length' does not exist on type 'Type'.Property 'length' does not exist on type 'Type'.
  return arg;
}

如果实现访问length属性,可以使用下面的方法:

function loggingIdentity<Type>(arg: Type[]): Type[] {
  console.log(arg.length);
  return arg;
}

泛型类

在类中使用泛型与在函数中使用差别不大,同样在函数名称右边使用尖括号包裹,如下:

class GenericNumber<NumType> {
  zeroValue: NumType;
  add: (x: NumType, y: NumType) => NumType;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};

let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function (x, y) {
  return x + y;
};

泛型约束

在上面的loggingIdentity例子中,我们尝试访问length属性,typescript编译器抛出了警告,因为length并不是所有类型所共有的属性,我们通过入参为type[]解决了这个问题。但是当我们要求入参非必须是数组的同时又能够访问length属性那有该如何写呢?请看下面代码:

interface Lengthwise {
  length: number;
}

function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
  console.log(arg.length); // Now we know it has a .length property, so no more error
  return arg;
}

通过extends关键字我们能够约束泛型必须带有length属性,而此时入参类型如果没有length属性,typescript就会抛出警告:

loggingIdentity(3); // Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.

在泛型是使用类作为类型参数

当我们编写一个工厂函数,参数是类,返回值是类的实例,如下:

function createInstance<Type>(Class: { new (): Type }): Type {
  return new Class()
}

在jsx中使用泛型

有时候我们在再开发react组件时也需要用到泛型,同时在使用组件的时候也要传递类型参数,请看下面代码:

function DatePicker<DateType,>() {
    return <div>...</div>
}

<DatePicker<Moment> {...props} />
<DatePicker<Dayjs> {...props} />

需要注意的是,在jsx中使用泛型,需要加一个逗号:<DateType,>,因为jsx中会把<DateType>理解为实例化一个组件。

总结

以上就是泛型的全部内容了。泛型的存在能够让我们更灵活的运用类型去做更多事情,而不会觉得很多javascript中能做的事在typescript中却无法使用类型描述出来。