泛型介绍
在使用 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
是泛型的常用名称,但可以根据需要对其进行命名。
指定类型变量后,可以使用它来代替参数中的类型、返回类型或将其置于函数中要添加类型批注的任何其他位置。
类型变量 T 可用于任何需要类型批注的位置。 在 getArray 函数中,它用于指定 items 参数的类型、函数返回类型和返回新的项数组。
若要调用函数并向其传递类型,请将 <type>
追加到函数名称。 例如,getArray<number>
指示函数仅接受 number
值的数组,并返回 number
值的数组。 因为类型已指定为 number
,所以 TypeScript 会预期将 number
值传递给函数,如果传递的是其他值,则会引发错误。
备注
如果在调用函数时省略类型变量,TypeScript 将推断类型。 但是,这仅适用于简单数据。 如果传入数组或对象,会导致推断任何类型并消除类型检查。
在此示例中,通过更新 numberArray
和 stringArray
的变量声明以调用具有所需类型的函数,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
函数接受 value
和 message
两个参数,并返回 value
参数。 可以使用 T
和 U
两个泛型为每个参数和返回类型分配不同的类型。 通过调用 identity
函数初始化变量 returnNumber
(将 <number, string>
作为 value
和 message
参数的类型)、初始化 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'