# TypeScript 泛型
原因:
为什么要有泛型变量、泛型函数、泛型类型呢?
# 概念:
泛型是什么:TODO
泛型变量:常用 T
表示,当然,你也可以使用 U
或任意
泛型类型:泛型 T
作为元素,整体作为其他类型子元素类型的情况
泛型类:class GenericNumberClass <T>
,叫 泛型类
,(2020年12月20日 02:59:53 发现 deno
无法运行 泛型类
demo)
泛型约束:存在非法访问属性的情况,比如 .length
# 泛型的提出
在 TypeScript
中,为了解决函数的 输入类型
等同于 返回类型
而提出的一种新的声明方法
如果想要实现,输入的类型等于输出的类型,可能这么写,实例 1:
function inputTypeEqualOutputType(args: number): number {
return args;
}
console.log("ret==>", inputTypeEqualOutputType(2020));
如果想返回其他类型,就不得不再写一遍,定义输入和输出类型一致的函数声明。
再或者,使用 any
来实现:
function useAnyType(args: any): any {
return args;
}
console.log("any type:", useAnyType("any type"));
为了重用类型声明,以及表述输入和输出类似是一致的情况,TypeScript 提出的一种新的类型声明的方式——泛型。
常用 <T>
替代,现在改写实例 1:
function inputTypeEqualOutputType<T>(args: T): T {
return args;
}
console.log("ret==>", inputTypeEqualOutputType(2020));
这样就只需要写一遍,就可以支持多种入参类型使用:
console.log("1. null:", inputTypeEqualOutputType(null));
console.log("2. undefined:", inputTypeEqualOutputType(undefined));
console.log("3. string:", inputTypeEqualOutputType("2020"));
console.log("4. number:", inputTypeEqualOutputType(2020));
console.log("5. boolean:", inputTypeEqualOutputType(true));
console.log("6. symbol:", inputTypeEqualOutputType(Symbol(11)));
console.log("7. object:", inputTypeEqualOutputType({year:2020}));
console.log("8. function:", inputTypeEqualOutputType(()=>{}));
console.log("9. array:", inputTypeEqualOutputType([2020]));
以上的完整写法,复杂的情况可能需要完整写法,一般情况,编译器可自动推断:
console.log("1. null:", inputTypeEqualOutputType<null>(null));
console.log("2. undefined:", inputTypeEqualOutputType<undefined>(undefined));
console.log("3. string:", inputTypeEqualOutputType<string>("2020"));
console.log("4. number:", inputTypeEqualOutputType<number>(2020));
console.log("5. boolean:", inputTypeEqualOutputType<boolean>(true));
console.log("6. symbol:", inputTypeEqualOutputType<symbol>(Symbol(11)));
console.log("7. object:", inputTypeEqualOutputType<object>({year:2020}));
console.log("8. function:", inputTypeEqualOutputType<Function>(()=>{}));
console.log("9. array:", inputTypeEqualOutputType<number[]>([2020]));
# 泛型变量
但是,此时的泛型,其实等同于入参类型是 any
,返回类型也是 any
,这意味着,输入类型是数字,打印 .length
是错误的:
function inputTypeEqualOutputType(args: any): any {
console.log("length==>",args.length)
return args;
}
console.log("error==>:", inputTypeEqualOutputType(2020));
# T 作为其他类型的元素类型
T
类型也可以作为单独的元素类型被其他类型使用,比如数组:
function defineByArray<T>(arg:T[]):T[]{
console.log("arg array length ==>",arg.length)
return arg
}
console.log("array element type T==>",defineByArray([2020,3020]))
# 泛型类型
声明泛型类型:
function defineType<T>(arg:T):T{
return arg
}
let myDefineType:<T>(arg:T)=>T=defineType // 使用变量来接受函数
使用带有调用签名的对象字面量来定义泛型函数:
function defineTypeObject<T>(arg:T):T{
return arg
}
let myDefineTypeObject: {<T>(arg:T):T}=defineTypeObject
# 泛型接口
与此同时,也可以将对象字面量单独拎出来作为一个接口使用,于是就有了泛型接口:
interface DefineType{
<T>(arg:T):T;
}
function defineType<T>(arg:T):T{
return arg
}
let myDefineType:DefineType=defineType
把泛型参数作为整个接口的参数,让用户知道具体是哪个泛型类型 (比如:DefineType<string>
而非 DefineType
),使得接口里的其他成员也能知道参数的类型:
interface DefineType<T>{
(arg:T):T
}
function defineType<T>(arg:T):T{
return arg
}
let myDefineType:DefineType<number>:defineType
此时,不再描述泛型函数,而是把非泛型函数前面作为泛型类型的一部分,当使用 define
函数时候,还得需要传入类型参数来指定泛型类型 (这里是:number
),锁定了之后代码里使用的类型。
这对于描述 哪部分类型
属于 泛型部分
,理解 何时把参数
放在 调用签名里
和 何时放在接口上
是很有帮助的。
# 泛型类
无法创建
泛型枚举
无法创建
泛型命名空间
泛型类
与 泛型接口
差不多,泛型类使用 <>
括起泛型类型,跟在类名后面:
(注意:此处使用 Deno
运行,发现错误是 Property 'zeroValue' has no initializer and is not definitely assigned in the constructor.
)
class GenericNumberClass <T>{
zeroVal :T,
add:(x:T,y:T)=>T
}
let myGenericNumberClass = new GenericNumberClass<number>()
myGenericNumberClass.zeroVal =0
myGenericNumberClass.add = function(x,y) {
return x +y
}
此时,GenericNumberClass
并非只限制使用 number
类型,也可以使用 string
:
class GenericNumberClass <T>{
zeroVal :T,
add:(x:T,y:T)=>T
}
let myGenericString = new GenericNumberClass<string>()
myGenericString.zeroVal = "2020"
myGenericString.add = function(x,y) {
return x +y
}
console.log(myGenericString.add(myGenericString.zeroVal,"hello world"))
类包含两部分:静态部分
与 实例部分
,泛型类
属于实例部分的类型,所以类的静态属性不能使用这个 泛型类型
。
# 泛型约束
泛型声明,编译器并不能完全保证错误,比如当访问非法的属性时:
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // number 不存在 length
return arg;
}
loggingIdentity(2020)
所以,这需要我们限制函数,不要任意去访问带 .length
属性的所有类型。
仅当存在这个 .length
属性时,在允许,于是出现了 泛型约束
,下面是使用 extends
关键词实现约束:
interface LengthWise{
length:number
}
function lengthFn<T extends LengthWise>(arg:T):T{
console.log("T length ==>",arg.length)
return arg
}
console.log("number no length ==>",lengthFn(11)) // 错误
console.log("string has length ==>",lengthFn("11"))
console.log("string has length ==>",lengthFn({length:11,age:99}))
当不使用 泛型约束
前,编译器报错 error TS2339: Property 'length' does not exist on type 'T'.
使用了 泛型约束
后,编译器报错 error TS2345: Argument of type '11' is not assignable to parameter of type 'LengthWise'.
# 在泛型约束中使用类型参数
声明 类型参数
且被另外一个类型参数所约束。比如从对象中获取某个属性名,此时需要确保存在这个属性名:
function getProp<O, K extends keyof O>(obj: O, key: K) {
return obj[key];
}
const x1 = { a: 1, b: 22, c: 33 };
console.log("a==>", getProp(x1, "a"));
console.log("m==>",getProp(x,"m")) // 错误:Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c"'. console.log("m==>",getProp(x1,"m"))
# 泛型里使用类类型
TypeScript
使用泛型创建工厂函数是,需要引用构造函数的类类型,比如:
function CreateClass<T>(c:{new():T}):T{
return new c()
}
使用原型类型推断并约束 构造函数
与 类实例
的关系:
class AClass {
aName:boolean
}
class BClass {
bName: string
}
class CClass {
cName: number
}
class DClass extends CClass {
dName: AClass
}
class EClass extends CClass {
eClass: AClass;
dName: BClass
}
function createInstance <A extends CClass>(c:new ()=>A):A{
return new c()
}
createInstance(EClass).cName.bName; // 类型检查
createInstance(DClass).cName.aName; // 类型检查