泛型
用来创建可重用的组件,一个组件可以支持多种不同类型的数据
泛型之 Hello World
我们先来定义一个identity函数。 这个函数会返回任何传入它的值。 你可以把这个函数当成是echo命令。
function identity(arg: any): any {
return arg;
}
使用any类型使这个函数可以接受任意类型的参数。
但是这样写有一个问题,就是不能表示接受的参数与返回的值类型是一致的,虽然我们知道这个函数返回的值就是它传入的值,它们的类型应该是一样的,但是我们还需要在函数中表示出来。
因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的。就是类型变量,它是一个表示类型的变量,它不表示任何值。
function identity<T>(arg: T): T {
return arg;
}
这里我们定义了一个类型变量 T。它可以帮助我们捕获传入的参数类型,之后我们又将 T 设置为返回值的类型,这样我们就可以保证传入和返回值的类型是一致的了 —— 就是 T 表示的参数类型。 我们把这个版本的identity函数叫做泛型,因为它可以适用于多个类型。 定义好泛型后,我们有两种方法可以使用: 1、我们将类型变量的类型作为参数传入到函数中,用< >括起来,明确指定参数变量的类型
let output = identity<string>("myString");
2、编译器会根据传入的参数自动地帮助我们确定T的类型,即类型推论:
let output = identity("myString");
使用泛型变量
参考上面的例子,如果我们同时想打印出arg的长度,就会报错。
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // 报错: T 不存在 .length 方法
return arg;
}
之所以会报这个错,是因为 T 代表的是任意类型,而使用这个方法的人有可能会传入一个number类型的参数,number是没有 .length属性的。
假设我们想操作的是 T 类型的数组,数组是有 .length 属性的,我们修改一下上面的例子
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // 数组有 `.length` 属性,不报错
return arg;
}
也可以写成这样:
function loggingIdentity<T>(arg: <Array>T): <Array>T {
console.log(arg.length); // 数组有 `.length` 属性,不报错
return arg;
}
你可以这样理解loggingIdentity的类型:泛型函数loggingIdentity,接收类型参数T和参数arg,它是个元素类型是T的数组,并返回元素类型是T的数组。 如果我们传入数字数组,将返回一个数字数组,因为此时T的的类型为number。
泛型类型
泛型函数的类型与非泛型函数的类型没什么不同,只是有一个类型参数在最前面,像函数声明一样,我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以,比如上面例子中的 T 也可以写成 U,没有什么不同。
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <U>(arg: U) => U = identity;
我们也可以通过接口来定义函数的参数类型:
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
换个思路,我们也可以把泛型参数当成整个接口的一个参数,这样我们就能清楚的知道使用的具体是哪个泛型类型,接口内的其他成员也知道这个参数的类型了:
// 将泛型参数<T>作为接口的参数
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
注意:除了泛型接口,我们还可以创建泛型类。 注意,无法创建泛型枚举和泛型命名空间。
泛型类
泛型类看上去与泛型接口差不多。 泛型类使用(<>)括起泛型类型,跟在类名后面。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
我们在类那节说过,类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。
泛型约束
在下面我们曾经看到的例子中,我们想获取arg的 .length 属性,但是报错了,因为arg有可能没有 .length 属性,
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
当我们想要去处理具有 .length 属性的所有类型,只要有这个属性,我们就允许,为此,我们需要列出对T的约束要求。
我们应该创建一个接口来描述约束条件:创建一个包含length属性的接口,使用这个接口和extends关键字来实现约束:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // 现在我们可以确定 arg 一定具有 .length 属性,所以不会再报错
return arg;
}
在泛型约束中使用类型参数
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // okay
getProperty(x, "m"); // error: m 不是变量 x 的 key 值。
分析一下这个例子: 函数getProperty 有两个类型参数,一个是T一个是包含泛型约束的 K,它的约束条件是 keyof T 就是必须是 T 中的某个 key 值,
getProperty 的参数列表中的两个参数 obj,key 分别需要满足 T 和 K 这两个类型参数, getProperty(x, "m")不满足这个条件,因为 m 不是变量 x 的 key 值。