TS 泛型
示例代码:typescript-demos
泛型的重要性
“泛型是 TypeScript 中非常重要的一个概念,因为在之后实际开发中任何时候都离不开泛型的帮助,原因就在于泛型给予开发者创造灵活、可重用代码的能力。”1
“泛型是 TypeScript(以下简称 TS) 比较高级的功能之一,理解起来也比较困难。泛型应用场景非常广泛,很多地方都能看到它的影子。平时我们阅读开源 TS 项目源码,或者在自己的 TS 项目中使用一些第三方库(比如 React)的时候,经常会看到各种泛型定义。如果你不是特别了解泛型,那么你很可能不仅不会用,不会实现,甚至看不懂这是在干什么。”2
泛型是什么
“软件工程中,我们不仅要创建一致的定义良好的 API,同时也要考虑可重用性。组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。”3
适用场景
两个参考标准
- 当你的函数、接口或类将处理多种数据类型时
- 当函数、接口或类在多个地方使用该数据类型时
示例 1
首先我们来定义一个 通用 的 identity
函数,接收一个参数并直接返回它:
function identity(value) {
return value;
}
假如设定参数 value
为 number
类型,那么当传 string
就没用了。那很简单,改为 any
类型不就行了?这样当然也不行:
在静态编写的时候并不确定传入的参数到底是什么类型,只有当在运行时传入参数我们才能确定,这就导致了:
- 失去了定义应该返回哪种类型的能力
- 编译器失去了类型保护的作用
那么我们就需要变量,这个变量代表了传入的类型,然后再返回这个变量,它是一种特殊的变量,只用于表示类型而不是值。这个类型变量在 TS 中就叫做 泛型:
function returnItem<T>(params: T): T {
return params;
}
在函数名称后面声明泛型变量 <T>
,它用于捕获开发者传入的参数类型,然后就可以使用 T
作为参数类型和返回值类型。
所以,上述问题的解决方法就是使用泛型,写法如下:
function identity <T>(value: T): T {
return value;
}
console.log(identity(123)); // OK
console.log(identity('abc')); // OK
VSC 光标悬停调用函数名称,效果如下:
当我们调用
identity<number>(123)
时,number
类型就会像参数一样,在出现T
的任何位置填充该类型。
说明
<T>
中的T
被称为 类型变量,是我们希望传递给函数的类型占位符T
代表 Type,在定义泛型时通常用作第一个类型变量名称T
可以用任何有效名称代替,常见泛型变量代表还有:K
(Key):表示对象中的键类型V
(Value):表示对象中的值类型E
(Element):表示元素类型
示例 2
如何引入希望定义的任何数量的类型变量?
例如引入一个新的类型变量 U
,用于扩展我们定义的 identity
函数:
function identity<T, U>(value: T, message: U): T {
console.log(message);
return value;
}
console.log(identity<number, string>(68, "Tony")); // OK
注意到上面返回类型只有 T
,那么如果要返回两种类型呢?——使用 元组。
function identity<T, U>(value: T, message: U): [T, U] {
console.log(message);
return [value, message];
}
console.log(identity<number, string>(68, "Tony")); // OK
示例 3
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // => ['seven', 7]
除了元组还有其他更好的方案?——使用 泛型接口。
泛型接口
示例
// 定义一个用于 `identity` 函数的通用接口 `Identities`
interface Identities<V, M> {
value: V,
message: M
}
function identity<T, U> (value: T, message: U): Identities<T, U> {
console.log(value + ': ' + typeof(value));
console.log(message + ': ' + typeof(message));
let identities: Identities<T, U> = {
value,
message
}
return identities;
}
console.log(identity(68, 'Tony')); // OK
泛型类
泛型除了可以应用在函数和接口外,也可以应用在类中,还可以作用于类的成员函数。
在类中使用泛型,只需在类名后面,使用 <T, ...>
的语法定义任意多个类型变量:
interface GenericInterface<U> {
value: U,
getIdentity: () => U
}
// `IdentityClass` 实现 `GenericInterface<T>`,
// 当 `T` 表示 `number` 类型时,等于 `IdentityClass` 实现了 `GenericInterface<number>`
class IdentityClass<T> implements GenericInterface<T> {
value: T;
constructor(value: T) {
this.value = value;
}
getIdentity(): T {
return this.value;
}
}
const myNumberClass = new IdentityClass<number>(68);
console.log(myNumberClass.getIdentity()); // => 68
const myStringClass = new IdentityClass<string>('Tony');
console.log(myStringClass.getIdentity()); // => Tony