返回介绍

泛型 generic

发布于 2025-05-03 21:35:25 字数 4816 浏览 0 评论 0 收藏

初识泛型

我们需要一个变量,这个变量代表了传入的类型,然后再返回这个变量,它是一种特殊的变量,只用于表示类型而不是值。

这个类型变量在 TypeScript 中就叫做「泛型」。

function returnItem<T>(para: T): T {
    return para
}

我们在函数名称后面声明泛型变量 <T> ,它用于捕获开发者传入的参数类型(比如说 string ),然后我们就可以使用 T (也就是 string ) 做参数类型和返回值类型

多个类型参数

定义泛型的时候,可以一次定义多个类型参数,比如我们可以同时定义泛型 T 和 泛型 U

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

swap([7, 'seven']); // ['seven', 7]

泛型变量

泛型变量 T 当做类型的一部分使用,而不是整个类型

function getArrayLength<T>(arg: Array<T>) {
  
  console.log((arg as Array<any>).length) // ok
  return arg
}

泛型接口

泛型也可用于接口声明,以上面的函数为例,如果我们将其转化为接口的形式。

interface ReturnItemFn<T> {
    (para: T): T
}

泛型类

泛型除了可以在函数中使用,还可以在类中使用,它既可以作用于类本身,也可以作用与类的成员函数。

class Stack<T> {
    private arr: T[] = []

    public push(item: T) {
        this.arr.push(item)
    }

    public pop() {
        this.arr.pop()
    }
}

泛型类看上去与泛型接口差不多, 泛型类使用 <> 括起泛型类型,跟在类名后面。

泛型约束

上面的类泛型 T 可以是任意类型,如果我们要约束这个类型范围,可以用 <T extends xx> 的方式

type Params = number | string;

class Stack<T extends Params> {
  private arr: T[] = [];

  public push(item: T) {
    this.arr.push(item)
  }

  public pop() {
    this.arr.pop();
  }
}

泛型约束与索引类型

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

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
}

比如我们传入以下对象:

const a = {
  name: 'xiaomuzhu',
  id: 1
}

这个时候 getValue 第二个参数 key 的类型被约束为一个联合类型 name | id ,他只可能是这两个之一

使用多重类型进行泛型约束

我们刚才学习了通过单一类型对泛型进行约束的方式,那么我们再设想以下场景,如果我们的泛型需要被约束,它只被允许实现以下两个接口的类型呢?

interface FirstInterface {
  doSomething(): number
}

interface SecondInterface {
  doSomethingElse(): string
}

我们或许会在一个类中这样使用:

class Demo<T extends FirstInterface, SecondInterface> {
  private genericProperty: T

  useT() {
    this.genericProperty.doSomething()
    this.genericProperty.doSomethingElse() // 类型“T”上不存在属性“doSomethingElse”
  }
}

但是只有 FirstInterface 约束了泛型 TSecondInterface 并没有生效,上面的方法并不能用两个接口同时约束泛型,那么我们这样使用呢:

class Demo<T extends FirstInterface, T extends SecondInterface> { // 标识符“T”重复
  ...
}

上述的语法就是错误的,那么应该如何用多重类型约束泛型呢?

比如我们就可以将接口 FirstInterfaceSecondInterface 作为超接口来解决问题:

interface ChildInterface extends FirstInterface, SecondInterface {

}

这个时候 ChildInterfaceFirstInterfaceSecondInterface 的子接口,然后我们通过泛型约束就可以达到多类型约束的目的。

class Demo<T extends ChildInterface> {
  private genericProperty: T

  useT() {
    this.genericProperty.doSomething()
    this.genericProperty.doSomethingElse()
  }
}

我们还可以利用交叉类型来进行多类型约束,如下:

interface FirstInterface {
  doSomething(): number
}

interface SecondInterface {
  doSomethingElse(): string
}

class Demo<T extends FirstInterface & SecondInterface> {
  private genericProperty: T

  useT() {
    this.genericProperty.doSomething() // ok
    this.genericProperty.doSomethingElse() // ok
  }
}

以上就是我们在多个类型约束泛型中的使用技巧。

泛型与 new

我们假设需要声明一个泛型拥有构造函数,比如:

function factory<T>(type: T): T {
  return new type() // This expression is not constructable.
}

编译器会告诉我们这个表达式不能构造,因为我们没有声明这个泛型 T 是构造函数,这个时候就需要 new 的帮助了。

function factory<T>(type: {new(): T}): T {
  return new type() // ok
}

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。