泛型介绍

已完成

使用 TypeScript 生成 JavaScript 应用程序学习路径中之前的模块中,你了解了如何将类型批注应用于接口、函数和类以创建强类型化组件。 但是,如果想要创建可处理多种类型而不只是一种类型的组件,该怎么操作? 可以使用 any 类型,但这样就失去了 TypeScript 类型检查系统的功能。

泛型是可以在代码库中定义和重复使用的代码模板。 它们提供了一种方法,可用于指示函数、类或接口在调用时要使用的类型。 可以通过将参数传递给函数的方式来理解,不同之处是使用泛型可以指示组件在被调用时应该使用哪种类型。

当代码是满足以下条件的函数或类时,创建泛型函数:

  • 处理各种数据类型。
  • 在多个位置使用该数据类型。

泛型可以:

  • 在处理类型时提供更大的灵活性。
  • 实现代码重用。
  • 减少使用 any 类型的需要。

为什么使用泛型?

为更好地理解使用泛型的理由,可以查看一个示例。

getArray 函数生成 any 类型的项的数组。

function getArray(items : any[]) : any[] {
    return new Array().concat(items);
}

然后,调用 getArray 函数并向其传递一个数字数组来声明 numberArray 变量,并使用一个字符串数组来声明 stringArray 变量。 但是,由于使用了 any 类型,因此没有什么可以阻止代码将 string 推送到 numberArray 或将 number 推送到 stringArray

let numberArray = getArray([5, 10, 15, 20]);
let stringArray = getArray(['Cats', 'Dogs', 'Birds']);
numberArray.push(25);                       // OK
stringArray.push('Rabbits');                // OK
numberArray.push('This is not a number');   // OK
stringArray.push(30);                       // OK
console.log(numberArray);                   // [5, 10, 15, 20, 25, "This is not a number"]
console.log(stringArray);                   // ["Cats", "Dogs", "Birds", "Rabbits", 30]

如果想要在调用函数时确定数组将包含的值的类型,然后让 TypeScript 对传递给它的值进行类型检查以确保它们属于该类型,该怎么操作? 这时泛型就可以发挥作用了。

此示例使用泛型重写 getArray 函数。 现在,它可以接受你在调用函数时指定的任何类型。

function getArray<T>(items : T[]) : T[] {
    return new Array<T>().concat(items);
}

泛型定义一个或多个“类型变量”来标识要传递给组件的一个或多个类型(用尖括号 (< >) 括起来)。 (你还会看到称为类型参数或泛型参数的类型变量。)在上面的示例中,函数中的类型变量称为 <T>T 是泛型的常用名称,但可以根据需要对其进行命名。

指定类型变量后,可以使用它来代替参数中的类型、返回类型或将其置于函数中要添加类型批注的任何其他位置。

Diagram showing the getArray function with the T type variable following the function name.

类型变量 T 可用于任何需要类型批注的位置。 在 getArray 函数中,它用于指定 items 参数的类型、函数返回类型和返回新的项数组。

若要调用函数并向其传递类型,请将 <type> 追加到函数名称。 例如,getArray<number> 指示函数仅接受 number 值的数组,并返回 number 值的数组。 因为类型已指定为 number,所以 TypeScript 会预期将 number 值传递给函数,如果传递的是其他值,则会引发错误。

备注

如果在调用函数时省略类型变量,TypeScript 将推断类型。 但是,这仅适用于简单数据。 如果传入数组或对象,会导致推断任何类型并消除类型检查。

在此示例中,通过更新 numberArraystringArray 的变量声明以调用具有所需类型的函数,TypeScript 可阻止将无效项添加到数组中。

let numberArray = getArray<number>([5, 10, 15, 20]);
numberArray.push(25);                      // OK
numberArray.push('This is not a number');  // Generates a compile time type check error

let stringArray = getArray<string>(['Cats', 'Dogs', 'Birds']);
stringArray.push('Rabbits');               // OK
stringArray.push(30);                      // Generates a compile time type check error

使用多个类型变量

泛型组件中并不是只能使用单个类型变量。

例如,identity 函数接受 valuemessage 两个参数,并返回 value 参数。 可以使用 TU 两个泛型为每个参数和返回类型分配不同的类型。 通过调用 identity 函数初始化变量 returnNumber(将 <number, string> 作为 valuemessage 参数的类型)、初始化 returnString(以 <string, string> 作为参数的类型)和初始化 returnBoolean(以 <boolean, string> 作为参数的类型)。 使用这些变量时,TypeScript 可以对这些值进行类型检查并在发生冲突时返回编译时错误。

function identity<T, U> (value: T, message: U) : T {
    console.log(message);
    return value
}

let returnNumber = identity<number, string>(100, 'Hello!');
let returnString = identity<string, string>('100', 'Hola!');
let returnBoolean = identity<boolean, string>(true, 'Bonjour!');

returnNumber = returnNumber * 100;   // OK
returnString = returnString * 100;   // Error: Type 'number' not assignable to type 'string'
returnBoolean = returnBoolean * 100; // Error: Type 'number' not assignable to type 'boolean'