TypeScript 泛型
泛型简介
泛型在 TS 中可以说是一个非常重要的属性,它承载了从静态定义到动态调用的桥梁,同时也是 TS 对自己类型定义的元编程。泛型可以说是 TS 类型工具的精髓所在,也是整个 TS 最难学习的部分。
可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。
泛型定义
下面来创建第一个使用泛型的例子:identity
函数。 这个函数会返回任何传入它的值,不用泛型的话,这个函数可能是下面这样:
function identity(value: any): any {
return value;
}
使用 any
类型会导致这个函数可以接收任何类型的参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。
因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了 类型变量,它是一种特殊的变量,只用于表示类型而不是值。
function identity<T>(value: T): T {
return value;
}
我们给 identity
添加了类型变量 T
。 T
帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。 之后我们再次使用了 T
当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。
我们把这个版本的 identity
函数叫做泛型,因为它可以适用于多个类型。 不同于使用 any
,它不会丢失信息,像第一个例子那像保持准确性,传入数值类型并返回数值类型。
泛型使用
我们定义了泛型函数后,可以用两种方法使用。
1. 传入所有的参数,包含类型参数
function identity<T>(value: T): T {
return value;
}
let output = identity<string>("myString");
这里调用函数我们明确的指定了 T
是 string类型(identity<string>
),
这时 函数入参(value) 和 函数返回值 也必须都是 string类型,否则就会报错。
2. 类型推论
编译器会根据传入的参数自动地帮助我们确定 T
的类型 (普遍使用)
这里调用函数我们没有使用尖括号(<>
)来明确地传入类型,
这时编译器可以自动推论出 myString
的类型,然后把 T
设置为它的类型。
类型推论帮助我们保持代码精简和高可读性。如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入 T
的类型,在一些复杂的情况下,这是可能出现的。
3. 定义多个类型变量
其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。
比如我们引入一个新的类型变量 U
,用于扩展我们定义的 identity
函数:
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity<Number, string>(68, "Semlinker"));
利用上面 类型推论
可简写为:
identity<Number, string>(68, "Semlinker");
// 可省略尖括号,简写为:
identity(68, "Semlinker");
4. 使用泛型变量
如果我们想同时打印出 value 的长度。 我们很可能会这样做:
function identity<T>(value: T): T {
console.log(value.length); // 报错:类型 “T” 上不存在属性 “length”
return value;
}
如果这么做,编译器会报错说 类型 “T” 上不存在属性 “length”
,但是没有地方指明 value 具有这个属性。
记住,这些类型变量代表的是任意类型,所以使用这个函数的人可能传入的是个数字,而数字是没有 .length
属性的。
此时,我们可以将 T
进一步设置成 T
类型的数组 即可:
function identity<T>(value: T[]): T[] {
console.log(value.length);
return value;
}
泛型函数identity
,接收类型参数 T
和 参数 value
,此时参数和返回值是 T
的数组。
这可以让我们把泛型变量 T
当做类型的一部分使用,而不是整个类型,增加了灵活性。
泛型接口
interface Identities<V, M> {
value: V;
message: M;
}
function identity<T, U>(value: T, message: U): Identities<T, U> {
let identities = {
value,
message,
};
return identities;
}
identity(68, 'Semlinker'); // {value: 68, message: "Semlinker"}
在上述的 Identities
接口中,我们引入了类型变量 V
和 M
,来进一步说明有效的字母都可以用于表示类型变量,之后我们就可以将 Identities
接口作为 identity
函数的返回类型