泛型怎么使用
当你不确定函数参数的类型时,你可能会这样写:
function identity(arg: any): any {
return arg;
}
但是如果想要函数参数和函数返回值的数据类型一致的时候又该怎么办呢。
我们可以使用类型变量来帮助更好地进行约束。
function identity<T>(arg: T): T{
return arg;
}
这里定义了一个类型变量T,T会帮我们捕获用户传入的类型。
调用使用泛型的函数通常有两种方式:
第一种,指定类型参数
let res = identity<string>("Hello");
第二种,使用类型推论
let res = identity("hello");
使用泛型变量
当你在函数上使用泛型变量时,编译器会要求你在函数体内正确地使用这个通用的类型,对于泛型变量的操作必须是通用的操作。比如当你希望对泛型变量arg进行.length
操作时,编译会报错Error: T doesn't have .length
,毕竟number
类型就没有.length属性。
但如果你想操作的是泛型数组而不是泛型变量,那么.length
属性就是存在的。
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // 数组拥有 .length属性, 因此没有错误
return arg;
}
泛型接口
在这里放几个示例。
interface GenericIdentityFn {
// 这看起来像是带有调用签名的对象字面量
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
我们也许会希望将泛型变量作为整个接口的一个参数,这样就能得知使用的具体是哪个泛型类型(比如Dictionary<string>
而不只是Dictionary
)。
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; };
泛型约束
有时我们不想要泛型指代的是所有类型,而是希望有所限制,比如它必须是一个带有.length
属性的类型。
为此,我们需要定义一个接口描述约束条件,并且使用extends
关键字来实现约束。
interface lengthwise{
length: number;
}
function loggingIdentity<T extends lengthwise>(arg: T): T{
console.log(arg.length);
return arg;
}
现在这个函数被定义了约束,因此不再能够适应所有类型,你必须传入一个含有.length
属性的值。
在泛型类型中使用类型参数
在 TypeScript 中利用泛型来构建工厂函数时,你需要明确指出用于创建对象的那个类的类型。
// 定义了一个泛型函数 create,其中 T 是一个泛型类型参数。
// 参数 c 要求是一个具有无参数构造函数且构造后得到的实例类型为 T 的类类型。
function create<T>(c: {new(): T; }): T {
// 在函数内部,通过使用 new c() 来创建并返回一个该指定类类型 c 的实例
// 也就是一个类型为 T 的对象。
return new c();
}