泛型
泛型变量
为何使用泛型?
- 在 类型声明时 无法确定 具体类型,仍然需要 对输入输出的参数 能有效提示 的情况下,定义 泛型变量 来约束类型,如果使用
any则 无法 确认函数输入输出之间的关系 以及对输出类型 校验 - 利用泛型对 更深层的子类型 进行明确,可以帮助开发过程获得更多提示
举个粗浅的🌰
//假设:str定义了string类型
let str = 'test';
//假设:并不确定传参,定义了any 并且 没有设置类型守卫
function doubleNum(_str:any):any{
return _str
}
//假设:..中间有一大堆逻辑
//此时方法传入了str any类型TS不会进行校验,此处得不到有效提示,
//到底传入一个string类型,这个方法要给我返回一个什么类型的值
//这个方法现在可以接受什么值也得不到提示
//好的。既然没有提示,那就command+左键快捷点到方法那里看一下逻辑~~~~
//可行。但这样来回切换确认类型和逻辑的关系挺累的
doubleNum(str)
TS 并不能给出有效提示,只有个any,返回到底是啥咱不知道:
现在换一种方式:
function doubleNum<T>(_str:T):T{
return _str
}
//现在泛型输入了string类型,TS识别并通过类型校验,知道返回值也会返回string
doubleNum<string>(str)
TS就能执行其类型校验的功能,并给出提示:
泛型变量语法
<T>表示,此处的 T 只是变量,可以任意命名,约定俗成:- T (type)表示类型
- K (key)表示对象键的类型
- V (value)表示对象值的类型
- E (element)表示元素的类型
- 泛型变量可以在类型分配或变量方法赋值时给定具体类型
function identity<T>(arg: T): T { //当作整个类型使用 return arg; } //1.传入所有的参数,包含类型参数 let output = identity<string>("myString"); //2.利用TS类型推论,省略类型注解 let output1 = identity("myString");
泛型约束
实现方式:
- 使用
extends关键字function FunName<T extends SomeType>(someProps: T)- 泛型变量在类型声明时没有确定类型,会导致在类型分配环节 泛型表示的 类型范围 过大,可能和 逻辑冲突。因此需要对类型进行一定的约束保证实际使用时的逻辑匹配类型
- 如下:对object类型,使用 extends 进行泛型约束
function merge<T extends object, U extends object>(objA: T, objB: U) { return Object.assign(objA, objB); } const mergeObj = merge({ user: "Nikcy" }, { age: 30 }); //此时mergeObj会被 TS推断为两个对象的融合类型,因此使用mergeObj 会提示 user和age - 泛型约束能专注于所需要的具体结构,又使结构具备扩展灵活性
- 如下:泛型约束后,只需结构中包含length属性即可
interface Length { length: string } //继承接口的泛型 function loggingIdentity1<T extends Length>(arg: T): T { console.log(arg.length); return arg; } type Length0 = string; //继承已知类型 function loggingIdentity2<T extends Length0>(arg: T): T { console.log(arg.length); //字符串类型具备length return arg; }
- 使用
keyof操作符- 泛型变量 可存在多个,利用
keyof还能定义由泛型对象键名组成的联合类型
//1. function getVal<T>(obj: T, key: keyof T) { return obj[key]; } //2. function getVal<T, U extends keyof T>(obj: T, key: U) { return obj[key]; } console.log(getVal({ name: "Nicky", age: 30 }, "name")); //Nicky console.log(getVal(["a", "b", "c"], 1)); //b - 泛型变量 可存在多个,利用
交叉类型 & 多重泛型约束
- 交叉类型:用
&操作符将一些已知类型融合成一个新类型,此类型包含所有已知类型的特性interface Colorful { color: string; } interface Circle { radius: number; } type ColorfulCircle = Colorful & Circle; let colorC: ColorfulCircle = { color: 'red', radius: 10 } - 区别于联合类型。联合类型用
|操作符连接一些已知类型,此类型只表示类型之一的特性- 如果将联合类型用作返回值的类型声明,TS无法确认具体返回是哪一种类型(TS不会根据逻辑修正原始类型的定义),因此返回值会要求是多个类型的交集特性
interface Bird { fly(); layEggs(); } interface Fish { swim(); layEggs(); } function getSmallPet(): Fish | Bird { var back: Fish = { swim() {}, layEggs() {} } return back } //即使此方法中逻辑返回就是Fish类型,TS也只会识别类型声明的联合类型,依旧会判定其具备另一种类型可能 //因此要么同步修改类型声明,要么使用断言指定某一种具体类型 let pet = getSmallPet(); pet.layEggs(); // okay pet.swim(); // errors - 多重泛型约束:泛型 + 继承 + 交叉类型
interface Music { url: string } interface Movie { quoto: string; actors: string } class Artist <T extends Music & Movie> { //泛型类 constructor(public arg: T) { //采用修饰符传参默认为 this.arg = arg } } let art = new Artist({ url: 'outside', quoto: 'art is all', actors: 'Julie' }); art.arg.url; //类型为string
- 如果将联合类型用作返回值的类型声明,TS无法确认具体返回是哪一种类型(TS不会根据逻辑修正原始类型的定义),因此返回值会要求是多个类型的交集特性
泛型类型
泛型接口的声明方式
- 类型声明时使用泛型变量的接口
//define a generic type interface GenericType<T> { (param:T):T } //to use the generic type, needing a specific type let useGeneric0: GenericType<string> = function(a) { return a } //like this let genericType: Array<string> = ['1']; //利用泛型类型的Array声明了变量的具体类型并赋值 - 接口中泛型变量的位置
//1.这种接口类型声明的对象方法为泛型函数,但是其他属性有具体的类型注解 interface GenericIdentityFn { <T>(arg: T): T; noGener: string } //2.泛型变量定义在接口上时,此接口为泛型接口,此泛型变量可以作用于内部所有方法或属性注解上 interface GenericIdentitySpecify<T> { (arg: T): T; }
泛型函数的声明方式
- 函数注解时用泛型变量的为泛型函数
function literalFun<T>(params: T):T { return params } //对象字面量的泛型类型声明 let literalType: {<T>(params: T): T} = literalFun; //函数表达式的泛型类型声明 let funType: <T>(param: T) => T = literalFun
泛型函数 & 泛型接口 &泛型类
- 三种对比
//函数声明方式的泛型函数——类型注解+函数实现 function literalFun<T>(params: T):T { return params } //函数表达式方式的泛型函数 let literalFun:<T>(params: T)=> T = function(param){ return params } //泛型接口 interface GenericInter<T> { proper0: T; proper1: number; //... so on (arg: T)=> T; } //泛型类 class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; }
泛型类型 & 联合类型 区别
- 联合类型和泛型类型的具体类型只有在 运行时 才能确认
- 联合类型是多种类型的集合,逻辑处理搭配类型守卫才能准确区分各种类型的处理方式;如:string类型的length属性,number类型不具备
- 泛型类型用于指代 具备某种特性的单一类型,只强调类型中的必要结构,不强调类型;如:需要使用带有length的泛型,不在乎是string还是Array类型
扩展
- 泛型类型,
any类型,联合类型声明的函数区别://联合类型函数:写法复杂 function noGeneric0(x: number | string):number | string { return x } //泛型函数 function generic0<T>(param:T): T { return param } //任意类型函数:传参和返回值之间无法确定明确的类型关联 function generic1(x:any): any { return x + '' } //联合和泛型写法在编写及编译过程都有类型检查,返回理想类型,可以帮助规避运行时异常 console.log(typeof noGeneric0(1), typeof noGeneric0('1')); //number string console.log(typeof generic0(1), typeof generic0('1')); //number string //输入输出的参数类型之间没有关联,由函数逻辑决定,错误检查无效 console.log(typeof generic1(1), typeof generic1('1')); //string string keyof的使用- 作为TS2.1引入的操作符,可以用于获取某个类型中定义的所有键名,其返回类型是所有键名组成的联合类型
interface Person { name: string; age: number } type K0 = keyof Person; //num | age 字符串键名的联合类型 // let k0: K0 = 'other'; //error 不能将other分给 ‘name’ | 'age' let k0: K0 = 'name'; type K1 = keyof Array<Person> ; //泛型写法等价于Person[] let k1: K1; //// number | "length" | "push" | "concat" | ... type K2 = keyof { [x: string]: Person }; // string | number- 定义了泛型变量的类型,当用作类型声明的时候需要赋具体的类型,这种具体类型叫做类型参数
let output = identity<string>("myString"); // 此处传入的string叫做类型参数