TypeScript 泛型:完整指南

**TL;DR:** TypeScript 泛型允许开发人员编写可重复使用的代码,这些代码可以处理各种数据类型,同时保持类型安全。它们对于构建强大且可扩展的 TypeScript 应用程序至关重要。

TypeScript Generics

本文探讨了 TypeScript 泛型的基本知识,包括它们在函数、类和接口中的用法,并演示了它们如何使代码变得多功能且健壮。

什么是 Typescript 泛型?

Typescript 泛型可以使用占位符类型定义 Typescript 代码,使其灵活、可扩展、可重用,同时保持类型安全。

Typescript 在编译时以定义泛型类型的占位符进行类型安全检查。实现组件时,实际类型将替换占位符。这种技术使管理和减少重复变得更容易,因为您不需要为每种数据类型进行不同的实现。

如果没有泛型,您将编写多个版本的函数或类来处理不同的数据类型,从而导致代码重复。泛型允许单一实现可供各种类型重用,同时保留静态类型检查。

下一节中的代码示例将帮助您理解这种差异。

何时使用 Typescript 泛型?

泛型可用于 TypeScript 的不同部分,以帮助更有效地管理类型。它们在函数、接口、类和其他灵活性至关重要的结构中起着重要作用。

1. 函数中的泛型类型

泛型经常应用于函数中以减少冗余。例如,考虑一个以字符串或数字作为参数的函数。

function identity(value: any): any {
  return value;
}
const result1 = identity(42); // result1: any
const result2 = identity("hello"); // result2: any

此函数运行正常。但它使用 any 类型,这意味着 Typescript 无法跟踪具体类型。因此,返回值的类型为 **any**,并且 Typescript 无法再强制执行类型安全。如果我们需要保持类型安全,则必须编写两个不同的函数,其中一个返回字符串,另一个返回数字。但是,这种方法会增加代码重复。

我们可以通过使用泛型来保存类型信息来改进上述函数。

function identity(value: Type): T {
  return value;
}
const result1 = identity(42); // result1: number
const result2 = identity("hello"); // result2: string

**T** 表示该方法在这种情况下使用的类型。如果存在,Typescript 将确认输入类型和返回参数中的类型相同。

另外,我们可以定义函数而不明确定义参数类型。

const result3 = identity(100); // result3: number
const result4 = identity("world"); // result4: string

在 Typescript 中,当在单个函数或组件中使用多种类型时,您可以使用多个泛型类型参数。例如,您可能需要一个函数,该函数接受两种不同类型的输入并将它们作为一对返回。

function multipleParams(first: T, second: U): [T, U] {
 return [first, second];
}
const result1 = multipleParams("hello", 42); // result1: [string, number]
const result2 = multipleParams("hello", "world"); // result2: gives a type error

在这种情况下,该函数返回一个元组,其中第一个元素的类型为 **T**,第二个元素的类型为 **U**。这使得该函数能够对两种不同类型进行类型安全的处理。

2. Typescript 中的默认类型

在 Typescript 中,您可以为泛型提供默认类型,使其成为可选类型。如果没有提供类型,Typescript 将使用默认值。

function createArray(length: number, value: T): T[] {
 return Array(length).fill(value);
}

const stringArray = createArray(3, "hello"); // T defaults to string, so stringArray is a string array
const numberArray = createArray(3, 42); // T is explicitly set to a number, so numberArray is a number array

在这个例子中,类型参数**T**默认为字符串。如果开发人员在调用函数时没有指定具体类型,**T**将默认为字符串。

3. 通用接口

Typescript 泛型也可以应用于接口。假设你想定义一个 **Box** 接口,其值为 **任意** 类型。

interface Box {
  value: any;
}
const numberBox: Box = { value: 123 }; // correct
const stringBox: Box = { value: "hello" }; // correct

这更接近于泛型函数示例;由于我们没有定义特定类型,因此此代码也可以正常工作。但是,由于值的类型为 **any**,我们可能会遇到与类型相关的错误。

为了保护类型,我们可以在这里定义一个通用接口。

interface Box {
  value: Type;
}
const numberBox: Box = { value: 123 }; // number
const stringBox: Box = { value: "hello" }; // string
const stringBox2: Box = { value: 123 }; // incorrect

该接口是通用的,其值类型严格限制为 **Type** 变量。在创建实例时,可以将 **Type** 变量指定为数字或字符串,以便 Typescript 确保遵守适当的类型。

4. 泛型类

还可以使用泛型编写类来处理不同类型,同时保持类型安全。让我们创建一个可以存储和检索**任何**类型值的**存储**类。

class Storage {
  private data: any;
  setItem(item: any): void {
    this.data = item;
  }
  getItem(): any {
    return this.data;
  }
}
const storage = new Storage();
storage.setItem(123);
const item = storage.getItem();

这个类可以工作,但是由于数据是 **any** 类型,**getItem** 方法返回 **any**,从而消除了类型安全性。因此,我们可以使用泛型重写该类以提高类型安全性。

class Storage {
  private data: T;
  setItem(item: T): void {
    this.data = item;
  }
  getItem(): T {
    return this.data;
  }
}
const numberStorage = new Storage();
numberStorage.setItem(123);
const item = numberStorage.getItem();

在这种情况下,**Storage** 类使用类型 **T**。当您在创建实例时为它们定义类型时,Typescript 可确保数据正确。此代码示例中的 **getItem** 方法将产生一个数字。

5. 通用约束

您可以使用泛型约束来限制泛型可以接受的类型,确保它们具有特定的属性。

例如,如果您有一个需要访问输入的 **length** 属性的函数,则可以使用约束来确保只允许具有 **length** 属性的类型。这可以防止 Typescript 出现错误或让不兼容的类型溜走。

function printLength(value: T): void {
  console.log(value.length); // Error: Typescript doesn’t know if the value has length 
 }

这里,值 **T** 没有定义 **length** 属性。为了忽略这个问题,我们可以添加一个约束,指定 **T** 必须具有 **length** 属性。我们通过声明 **T** extends **{ length: number }** 来实现这一点。

function printLength(value: T): void {
  console.log(value.length); // Now Typescript knows length exists
}

printLength("hello"); // Output: 5
printLength([1, 2, 3]); // Output: 3
printLength({ length: 10 }); // Output: 10

现在,此函数将具有**length**属性;它不会给出任何错误并将根据输入的长度执行。

结论

Typescript 泛型允许您编写灵活、可回收且类型安全的代码。您可以使用这些泛型的类、方法和接口管理许多数据类型而无需重复代码。泛型约束、众多类型和默认类型是我们在本文中讨论的一些关键用例,并展示了每种用例如何提高程序的可扩展性和可维护性。

了解 Typescript 泛型可以帮助您编写更精确、适应性更强、类型安全的代码,从而使您的 Typescript 应用程序更加健壮。

相关博客

  • Webpack 与 Vite:哪个捆绑器适合您?
  • 使用 single-spa 构建微前端:指南
  • 使用 RxJS 掌握异步 JavaScript
  • Axios 和 Fetch API?选择正确的 HTTP 客户端