前端深刻理解Typescript泛型概念

整理一些我对于泛型的理解。

首先介绍一下泛性的概念

泛型程序设计(generic programming)是程序设计语言的一种风格或范式。泛型容许程序员在强类型程序设计语言中编写代码时使用一些之后才指定的类型,在实例化时做为参数指明这些类型。程序员

泛型是指在定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性。数组

先举一个简单的例子

假设咱们定义一个函数,它能够接收一个number类型作为参数,而且返回一个number类型。安全

function genericDemo(data: number): number {
    return data;
}
复制代码

按照以上的写法是没有问题的,可是若是咱们要接受一个string并返回一个string呢?若是逻辑同样还要在写一遍吗?就像下面这样。bash

function genericDemo(data: string): string {
    
    return data;
}
复制代码

这显然代码是很冗余的,咱们还有不使用any的写法吗?答案是显然易见的,可使用范型的写法,就像下面这样。ide

function genericDemo<T>(data: T):T {
    return data;
}
复制代码

咱们在函数名称genericDemo后面声明了范型变量<T>,他用于捕获调用该函数时传入的参数类型(例如:number),以后咱们就可使用这个类型。 以后咱们再次使用了T当作返回值类型。如今咱们能够知道参数类型与返回值类型是相同的了。这容许咱们跟踪函数里使用的类型的信息。函数

多个类型参数

咱们在定义范型的时候,也能够一次定义多个类型参数,像下面这样。学习

function swap<T, U>(tuple: [T, U]):[U, T] {
    return [tuple[1], tuple[0]];
}
复制代码

泛型接口

咱们先定义一个范型接口Identities,而后定义一个函数identities()来使用这个范型接口ui

interface Identities<T, U> {
    id1: T;
    id2: U;
}
复制代码

我在这里使用TU做为咱们的类型变量来演示任何字母(或有效的字母数字名称的组合)都是有效的类型—除了常规用途以外,您对它们的调用没有任何意义。spa

咱们如今能够将这个接口应用为identity()的返回类型,修改咱们的返回类型以符合它。咱们还能够console.log这些参数和它们的类型,以便进一步说明:设计

function identities<T, U> (arg1: T, arg2: U): Identities<T, U> {
   console.log(arg1 + ": " + typeof (arg1));
   console.log(arg2 + ": " + typeof (arg2));
   let identities: Identities<T, U> = {
    id1: arg1,
    id2: arg2
  };
  return identities;
}
复制代码

咱们如今对identity()所作的是将类型T和U传递到函数和identity接口中,从而容许咱们定义与参数类型相关的返回类型。

范型变量

使用泛型建立像identity这样的泛型函数时,编译器要求你在函数体必须正确的使用这个通用的类型。 换句话说,你必须把这些参数当作是任意或全部类型。

咱们先看下以前例子

function genericDemo<T>(data: T):T {
    return data;
}
复制代码

若是咱们想同时打印出data的长度。 咱们极可能会这样作

function genericDemo<T>(data: T):T {
    console.log(data.length); // Error: T doesn't have .length return data; } 复制代码

若是这么作,编译器会报错说咱们使用了data.length属性,可是没有地方指明data具备这个属性。 记住,这些类型变量表明的是任意类型,因此使用这个函数的人可能传入的是个数字,而数字是没有.length属性的。

如今假设咱们想操做T类型的数组而不直接是T。因为咱们操做的是数组,因此.length属性是应该存在的。 咱们能够像建立其它数组同样建立这个数组:

function genericDemo<T>(data: Array<T>):Array<T> {
    console.log(data.length);
    return data;
}
复制代码

范型类

咱们还能够在类属性和方法的意义上使类泛型。泛型类确保在整个类中一致地使用指定的数据类型。例以下面这种在React Typescript项目中的写法。

interface Props {
    className?: string;
    ...
}

interface State {
    submitted?: bool;
    ...
}

class MyComponent extends React.Component<Props, State> {
   ...
}
复制代码

咱们在这里使用与React组件一块儿使用的泛型,以确保组件的props和state是类型安全的。

泛型约束

咱们先看一个常见的需求,咱们要设计一个函数,这个函数接受两个参数,一个参数为对象,另外一个参数为对象上的属性,咱们经过这两个参数返回这个属性的值,好比:

function getValue(obj: object, key: string){
    return obj[key] // error
}
复制代码

咱们会获得一段报错,这是新手 TypeScript 开发者经常犯的错误,编译器告诉咱们,参数 obj 其实是 {},所以后面的 key 是没法在上面取到任何值的。

由于咱们给参数 obj 定义的类型就是 object,在默认状况下它只能是 {},可是咱们接受的对象是各类各样的,咱们须要一个泛型来表示传入的对象类型,好比T extends object:

function getValue<T extends object>(obj: T, key: string) {
  return obj[key] // error
}
复制代码

这依然解决不了问题,由于咱们第二个参数 key 是否是存在于 obj 上是没法肯定的,所以咱们须要对这个 key 也进行约束,咱们把它约束为只存在于 obj 属性的类型,这个时候须要借助到后面咱们会进行学习的索引类型进行实现 <U extends keyof T>,咱们用索引类型 keyof T 把传入的对象的属性类型取出生成一个联合类型,这里的泛型 U 被约束在这个联合类型中,这样一来函数就被完整定义了:

function getValue<T extends object, U extends keyof T>(obj: T, key: U) {
  return obj[key] // ok
}
复制代码

另外提一个多重泛型约束的写法,能够看成拓展:

interface firstInterface {
    first(): number
}

interface secondInterface {
    second(): string
}

class Demo<T extends firstInterface & secondInterface >{
    ...
}
复制代码

在泛型里使用类类型

在TypeScript使用泛型建立工厂函数时,须要引用构造函数的类类型。好比:

function create<T>(type: {new(): T; }): T {
    return new type();
}
复制代码

参数type的类型{new(): T}就表示此泛型T是可被构造的,在被实例化后的类型是泛型 T

总结

以上内容是我这段时间学习ts泛型的一些总结,但愿能够对读到的你有所帮助。 喜欢的同窗也能够关注。每周都会持续更新不一样的内容。

相关文章
相关标签/搜索