Generics 泛型

内容目录

Generics 泛型

为什么使用泛型

在 TypeScript 中,我们可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。这样用户就可以以自己的数据类型来使用组件。

一个简单例子:

function echo<T>(arg: T): T {
  return arg;
}

const result = echo(true);

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

const result1 = swap(["string", 1234]);
console.log(result1[0]); //1234
console.log(result1[1]); //string

上述的例子中,我们使用了两个类型参数,分别是 T 和 U。当我们调用 echo 时,即使每次传入的参数类型不同,我们都可以控制它返回值的类型是什么,保证一致性。

泛型约束

在一些使用场景中,我们需要对泛型的类型进行约束,比如我们想要获取对象的属性,但是这个属性可能不存在,这时候我们就可以使用泛型约束。

接下来是一个例子,在这个例子中,我们在一个泛型函数中使用了 length 属性,但是这个属性并不是所有类型都有的,所以我们需要对泛型进行约束,使得这个泛型函数可以接受所有包含 length 属性的类型。

不用泛型约束的方式:

//当使用length属性时,我们无法确定该类型是否有该参数,所以可以提前设置泛型为一个数组类型,但该方法有局限性。
function echoWithArr<T>(arg: T[]): T[] {
  console.log(arg.length);
  return arg;
}

const arrs = echo([1, 2, 3]);

上述的解决方式中,我们使用了泛型约束,但是这种方式有局限性,不仅失去了泛型的灵活性,而且还有可能不能满足我们的需求。

使用约束的方式:

interface IWithLength {
  length: number;
}

function echoWithLength<T extends IWithLength>(arg: T): T {
  console.log(arg.length);
  return arg;
}

我们使用了 extends 关键字,创建了一个接口 IWithLength,然后使用这个接口和 extends 关键字来约束泛型 T。要求所有使用这个泛型的函数必须传入的参数必须包含 length 属性。

泛型在类中的应用

我们可以在类中使用泛型,但是在类中使用泛型时,我们需要在类的实例属性上使用泛型,而不是在类的静态属性上使用泛型。

class Queue<T> {
  private data = [];
  push(item: T) {
    return this.data.push(item);
  }
  pop(): T {
    return this.data.shift();
  }
}

const queue = new Queue<number>();
queue.push(1);
console.log(queue.pop().toFixed()); //1
const queue2 = new Queue<string>();
queue2.push("str");
console.log(queue2.pop().length); //3

泛型在接口中的应用

我们同样可以在接口中使用泛型,例子如下:

interface KeyPair<T, U> {
  key: T;
  value: U;
}

let kp1: KeyPair<number, string> = { key: 1, value: "str" };
let kp2: KeyPair<string, number> = { key: "str", value: 123 };

let arr: number[] = [1, 2, 3];
let arrTwo: Array<number> = [1, 2, 3];

在上述的例子中,我们使用泛型让接口变得更加灵活,我们可以传入任意类型的参数。

同时,我们使用了两种方式来定义数组,一种是使用了类型别名,另一种是使用了泛型。这两种方式都可以定义数组,但是使用泛型的方式更加灵活,因为我们可以使用泛型来定义多维数组,而使用类型别名的方式则不行。

泛型在函数类型中的应用

我们可以使用泛型来定义函数类型,例子如下:

interface IPlus<T> {
  (a: T, b: T): T;
}

function plus(a: number, b: number): number {
  return a + b;
}

function connect(a: string, b: string): string {
  return a + b;
}

const a: IPlus<number> = plus;
const b: IPlus<string> = connect;

在上述的例子中,我们定义了一个接口 IPlus,这个接口的作用是用来约束函数的类型,我们使用了泛型来定义函数的类型,这样我们就可以使用这个接口来约束函数的类型,而不用每次都去定义一个新的接口。

相关笔记

Class 类

Class 类 基本概念 类:定义了一件事物的抽象特点,包含它的属性和方法。 对象:类的实例。 面向对象三大特性:封装(将操作过程隐藏起来,只向外暴露需要使用地接口)、继承(子类继承父类,子类可以拥有父类地所有成员,并且可以更进一步)、多态(一个方法的多个实现)。

阅读全文

interface 接口

interface 接口 接口(Interfaces)是 TypeScript 的一个重要概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。

阅读全文

function 函数

function 函数 函数是 JavaScript 中的基本组成部分,TypeScript 为 JavaScript 中的函数提供了完整的定义类型。

阅读全文