深入了解 TypeScript 泛型:第 1 部分 — 泛型介绍

147 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

In Depth Look at TypeScript Generics Part 1 Intro to Generics.png

在这个由两部分组成的系列中,我们将深入了解 TypeScript 泛型。第一篇文章将使您对泛型及其工作原理有一个基本的了解。在后面的文章中,我们将介绍高级推理和条件类型。

所以,事不宜迟,让我们开始吧!

为什么我们需要泛型?

使用泛型允许我们编写类型安全的代码,该代码将适用于各种函数和对象。

如果没有泛型,我们将不得不为我们想要使用的每个可能的数据组合创建一个新类型。使用泛型,我们可以编写一次函数或方法,然后将其重用于不同类型的输入。

听起来不错,但 TypeScript 泛型在实践中是什么样的?让我们看一下语法。

通用语法

TypeScript 使用尖括号<>和类型符号T来指示通用语法。在您的代码中,TypeScript 会将T替换为您传递的类型。

我们来看一个例子:

function identifyType <T>(target: T) {
  console.log("Type of target is", typeof target);
}

identifyType("LOL") // "Type of target is", "string"
identifyType({word: "LOL"}) // Type of target is", "object"

上面代码中的函数接受一个泛型类型的参数T。然后它使用操作符typeof将参数的类型打印到控制台。

在第一个调用中,我们传入一个字符串,在第二个调用中,我们传入一个对象。由于它是通用的,因此此函数将适用于任何数据,并且将在两个实例中成功执行。

类和接口也可以使用泛型:

class Identifier<T> {
  seed: T;
  constructor(public newSeed: T) {
    this.seed = newSeed;
  }

  identifyType<T>(target: T) {
    console.log("Type of target is", typeof target);
  }
}

这里我们定义了一个带有构造函数和identifyType方法的泛型类。该identifyType方法接受的参数类型必须与newSeed 提供给构造函数的参数相匹配。

换句话说,如果您将字符串传递给构造函数,则identifyType只会接受字符串作为参数。

旁注:您不必使用T来表示泛型。T只是 TypeScript 中常用的约定。

默认情况下,TypeScript 尝试根据参数推断类型,但您可以使用显式类型转换来使用括号<>语法强制特定类型:

identifyType<string>(1); // Argument of type 'number' is not assignable to parameter of type 'string'

在此示例中,该函数identifyType需要一个字符串类型的参数,但接收一个数字。由于这种不匹配,TypeScript 会产生错误。

约束类型

假设我们想限制identifyType函数只接受字符串和数字。以下是我们的做法:

  function identifyType<T extends string | number>(target: T) {
    console.log("Type of target is", typeof target);
  }

  identifyType(true) // Argument of type 'boolean' is not assignable to parameter of type 'string | number'

使用extends关键字,我们可以告诉 TypeScript 我们的函数或类应该接受哪些类型。在我们的例子中,identifyType只接受字符串或数字。当我们尝试传递布尔值时,该函数会产生错误。

使用类型 T

通用代码只能引用任何类型 T的 共有的对象的函数或属性。换句话说,您不能访问特定于特定类型的任何内容。您只能访问我们指定的所有泛型类型中存在的方法和属性。

例如,如果我们有一个泛型类型被限制为两种类型,我们只能使用它们中存在的函数和属性:

type birdGenerator = {
  generate: () => any[]
  birds: any[]
}

type ponyGenerator = {
  generate: () => any[]
  ponies: any[]
}

function generateAnimal<T extends birdGenerator | ponyGenerator> (generator: T) {
  generator.generate()
  generator.birds // ERROR: Property 'birds' does not exist on type 'ponyGenerator'.
}

在上面的示例中,如果尝试访问generateAnimal函数中的birds属性,我们会得到一个错误。但是我们可以毫无问题地访问generate方法,因为它同时存在于birdGeneratorponyGenerator类型中。

通用约束

您可以从另一个泛型类型构造一个泛型类型。一种方法是使用keyof关键字。

关键字用于根据keyof另一个类型的键生成新类型。

我们可以使用它来确保我们只能指定通用对象中存在的键,如下所示:

function getPropertyValue<T, B extends keyof T> (target: T, key: B) {
  return target[key];
}

const myObj = {
  id: "0",
  name: "John Doe",
  age: 19
}

getPropertyValue(myObj, "id")
getPropertyValue(myObj, SSN) // ERROR: Cannot find name 'SSN'.

结论

在本文中,我们介绍了 TypeScript 泛型的基础知识。我们研究了使用泛型来约束我们的代码接受的类型,并探索了使用泛型对象时可用的一些特性。