如何在TypeScript中使用泛型

来自菜鸟教程
跳转至:导航、​搜索

介绍

在编写动态和可重用代码时,遵循 Don't-Repeat-Yourself (DRY) 原则很重要。 使用泛型可以帮助您在 TypeScript 代码中实现这一目标。

使用泛型,您可以编写动态且可重用的泛型代码块。 此外,您可以将 TypeScript 中的泛型应用于类、接口和函数。

在本文中,您将把泛型集成到您的 TypeScript 代码中,并将它们应用到函数和类中。 您还将学习如何使用接口在 TypeScript 中为泛型添加约束。

先决条件

要成功完成本教程,您将需要以下内容:

  • 你的机器上安装了最新版本的 TypeScript。 这个 如何设置一个新的 TypeScript 项目 教程可以帮助你完成这个。
  • 安装了最新版本的ts-node。 如果你想测试和运行你的 TypeScript 代码,这是必要的。 这个 使用 ts-node 轻松运行 TypeScript 脚本 教程是一个很好的起点。
  • 熟悉在 TypeScript 中编写函数和类。

第 1 步 - 了解泛型

有时,您可能希望为不同的数据类型重复相同的代码块。 这是用于两种不同数据类型的相同函数的示例:

// for number type
function fun(args: number): number {
  return args;
}

// for string type
function fun(args: string): string {
  return args;
}

请注意,在此示例中,对于 numberstring 类型重复相同的功能。 泛型可以帮助您编写通用方法,而不是像上面的示例那样编写和重复相同的代码块。

有一种称为 any 的类型,您可以使用它来实现与代码中的泛型相同的效果。 使用 any 类型将允许您选择退出类型检查。 但是,any 不是 type-safe。 这意味着使用 any 可以给你一个例外。

要在实践中看到这一点,请将 any 类型应用于前面的代码示例:

function fun(args: any): any {
 return args;
}

numberstring 类型交换为 any 类型使函数通用。 但是有一个问题——使用 any 类型意味着 fun 函数可以接受任何数据。 结果,您也失去了类型安全性。

尽管使用 any 类型是一种使您的 TypeScript 代码更通用的方法,但它可能并不总是最好的选择。 在下一步中,您将探索创建类型安全泛型的不同选项。

第二步——创建类型安全的泛型

要创建类型安全的泛型,您需要使用 Type 参数。 Type 参数由 T<T> 定义。 它们表示传递给类、接口和函数的参数的数据类型。

返回到 fun 函数,使用 T 使您的泛型函数类型安全:

索引.ts

function fun<T>(args:T):T {
  return args;
}

因此,fun 现在是一个类型安全的泛型函数。 要测试此类型安全的泛型函数,请创建一个名为 result 的变量,并使用 string 类型参数将其设置为等于 fun。 参数将是 Hello World 字符串:

索引.ts

let result = fun<string>("Hello World");

尝试将 fun 函数与 number 类型一起使用。 设置参数等于 200

索引.ts

let result2 = fun<number>(200);

如果您想查看此代码的结果,可以包含 console.log 语句以将 resultresult2 打印到控制台:

索引.ts

console.log(result);
console.log(result2);

最后,您的代码应如下所示:

索引.ts

function fun<T>(args:T):T {
  return args;
}

let result = fun<string>("Hello World");
let result2 = fun<number>(200);

console.log(result);
console.log(result2);

使用 ts-node 在控制台中运行此 TypeScript 代码:

npx ts-node index.ts

代码呈现没有错误。 您将看到以下输出:

OutputHello World
200

您现在可以为具有一个参数的函数创建类型安全的泛型。 了解如何为具有多种不同类型的多个参数的函数创建泛型也很重要。

第 3 步 — 使用具有多种类型参数的泛型

如果一个函数中有很多参数,可以使用不同的字母来表示类型。 您不必只使用 T

参数.ts

function fun<T, U, V>(args1:T, args2: U, args3: V): V {
  return args3;
}

该函数接受 3 个参数,args1args2arg3,并返回 args3。 这些参数不限于某种类型。 这是因为 TUV 用作 fun 函数参数的泛型类型。

创建一个名为 result3 的变量并将其分配给 fun。 包括 <string, number, boolean> 类型以填充 TUV 泛型类型。 对于参数,包括一个字符串、一个数字和括号内的布尔值:

参数.ts

let result3 = fun<string, number, boolean>('hey', 3, false);

这将返回第三个参数 false。 要查看这一点,您可以使用 console.log 语句:

参数.ts

console.log(result3);

运行 ts-node 命令查看 console.log 语句输出:

npx ts-node params.ts

这将是输出:

Outputfalse

现在您可以为具有多个参数的函数创建泛型类型。 像函数一样,泛型也可以与 classesinterfaces 一起使用。

第四步——创建泛型类

像泛型函数一样,类也可以是泛型的。 与函数一样,使用尖括号 (<>) 中的 type 参数。 然后 <T> 类型在整个类中用于定义方法和属性。

创建一个同时接受 numberstring 输入的类,并使用这些输入创建一个数组。 使用 <T> 作为泛型类型参数:

类.ts

class customArray<T> {
  private arr: T[] = [];
}

现在,您的包含不同类型项目的数组已经到位。 创建一个名为 getItems 的方法,该方法返回 customArray 数组:

类.ts

getItems (arr: T[]) {
  return this.arr = arr;
}

创建一个名为 addItem 的方法,将新项目添加到 customArray 数组的末尾:

类.ts

addItem(item:T) {
  this.arr.push(item);
}

arr: T[] 参数意味着数组中的项目可以是任何类型。 因此 customArray 可以是数字、布尔值或字符串的数组。

添加一个名为 removeItem 的方法,用于从 customArray 中删除指定项:

类.ts

removeItem(item: T) {
  let index = this.arr.indexOf(item);
    if(index > -1)
      this.arr.splice(index, 1);
}

addItem 方法类似,removeItem 采用任何类型的参数,并从 customArray 数组中删除指定的参数。

现在泛型类 customArray 完成了。 为 numberstring 类型创建 customArray 的实例。

声明一个名为 numObj 的变量设置为 number 类型的 customArray 实例:

类.ts

let numObj = new customArray<number>();

使用 addItem 方法将数字 10 添加到 numObj

类.ts

numObj.addItem(10);

由于 customArray 是通用的,它也可以用于创建字符串数组。 创建一个名为 strObj 的变量,设置为字符串类型的 customArray 实例:

类.ts

let strObj = new customArray<string>();

使用 addItem 方法将字符串 Robin 添加到 strObj 数组中。

类.ts

strObj.addItem(“Robin”);

要查看代码的结果,请为 numObjstrObj 创建一个 console.log 语句:

类.ts

console.log(numObj);
console.log(strObj);

最后,您的代码应如下所示:

类.ts

class customArray<T> {
  private arr: T[] = [];

  getItems(arr: T[]) {
    return this.arr = arr;
  }

  addItem(item:T) {
    this.arr.push(item);
  }

  removeItem(item: T) {
    let index = this.arr.indexOf(item);
      if(index > -1)
        this.arr.splice(index, 1);
  }
}

let numObj = new customArray<number>();
numObj.addItem(10);

let strObj = new customArray<string>();
strObj.addItem(“Robin”);

console.log(numObj);
console.log(strObj);

运行 ts-node 后,您将收到以下输出:

OutputcustomArray { arr: [ 10 ] }
customArray { arr: [ 'Robin' ] }

您对 numberstring 类型都使用了 customArray 类。 您可以通过使用泛型类型来完成此操作。 但是,使用泛型确实有一些限制。 这将在下一步中讨论。

第 5 步 — 了解通用约束

到目前为止,您已经使用泛型创建了函数和类。 但是使用泛型有一个缺点。 要查看这个缺点,请编写一个名为 getLength 的函数,该函数将返回函数参数的 length

约束.ts

function getLength<T>(args: T) : number {
  return args.length;
}

只要传递的类型具有 length 属性,此函数就会起作用,但没有 length 属性的数据类型将引发异常。

这个问题有一个解决方案——创建通用约束。 为此,您首先需要创建一个名为 funcArgs 的接口并定义一个 length 属性:

约束.ts

interface funcArgs {
  length: number;
}

现在,更改 getLength 函数和 extend 它以包含 funcArgs 接口作为约束:

约束.ts

function getLength<T extends funcArgs>(args:T) : number {
  return args.length;
}

您已经使用接口创建了一个通用约束。 此外,您还使用此接口扩展了 getLength 功能。 它现在需要 length 作为必需参数。 使用没有长度参数的参数访问此 getLength 函数将显示异常消息。

要查看实际情况,请声明一个名为 result4 的变量并将其分配给 getLength,并以 3 作为其参数:

约束.ts

let result4 = getLength(3);

这将返回一个错误,因为 length 参数的值不包括在内:

Output⨯ Unable to compile TypeScript:
index.ts:53:25 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'funcArgs'.

53 let result4 = getLength(3);

要调用 getLength 函数,您需要包含 length 参数和 name 参数:

约束.ts

let result = getLength({ length: 5, name: 'Hello'});

这是调用我们函数的正确方法。 此调用具有 length 属性,您的函数将运行良好。 它不会显示任何错误消息。

结论

在本教程中,您成功地将泛型集成到您的 TypeScript 函数和类中。 您还包括了泛型的约束。

作为下一步,您可能有兴趣学习 如何将 TypeScript 与 React 一起使用。 如果您想了解如何在 VS Code 中使用 TypeScript,这篇 如何在 Visual Studio Code 中使用 TypeScript 文章是一个很好的起点。