1.1 泛型
泛型是可以在保证类型安全的前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class中。
需求:创建一个id函数,传入什么数据就返回该数据本身(也就是说,参数和返回值类型相同
function id(value:number):number {return value}
比如,id(10)调用以上函数就会直接返回10本身。但是,该函数只接收数值类型,无法用于其他类型
为了能让函数接收任意类型,可以将参数类型修改为any。但是,这样就失去了TS的类型保护,类型不安全
function id(value:any):any {return value}
泛型在保证类型安全(不丢失信息)的同时,可以让函数等与多种不同的类型一起工作,灵活复用。实际上,在C#和Java等编程语言中,泛型都是用来实现可复用组件功能的主要工具之一
1.2 创建泛型
function id<Type>(value:Type):Type{return value}
解释:
- 语法:在函数后面添加<>(尖括号),尖括号中添加类型变量,比如此处的Type
- 类型变量Type,是一种特殊类型的变量,它处理类型而不是值
- 该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)
- 因为Type是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型
- 类型变量Type,可以是任意合法的变量名称
1.3 调用泛型函数
function id<Type>(value:Type):Type {return value}
//调用
const num = id<number>(10)
const str = id<string>('a')
//函数类型Type,传入参数Value类型:Type,返回值类型:Typefunction id<Type>(Value:Type):Type{return Value
}const num=id<number>(10)
console.log(num)
//String类型
function id2<Type>(Value:Type):Type{return Value}
const num2=id2<string>("哈哈哈")
console.log(num2)
解释:
- 语法:在函数名称的后面添加<>(尖括号),尖括号中指定具体的类型,比如,此处的number
- 当传入类型number后,这个类型就会被函数声明时指定的类型变量Type捕捉到
- 此时,Type的类型就是number,所以,函数id参数和返回值的类型也都是number
同样,如果传入类型string,函数id参数和返回值的类型就都是string
这样,通过泛型就做到了让id函数与多种不同类型一起工作,实现了复用的同时又同时保证了类型安全
1.4 简化调用泛型函数
简化调用泛型函数:
function id<Type>(value):Type {
return value
}let num=id<number>(10)
//可以改进为
let num=id(10)
解释:
- 在调用泛型函数时,可以省略<类型>来简化泛型函数的调用
- 此时,TS内部会采用一种叫做类型参数推断的机制,来根据传入的实参自动推断出类型变量Type的类型
- 比如,传入的实参10,TS会自动推断出变量num的类型number,并作为Type的类型
推荐:使用这种简化的方式调用泛型函数,使代码更短,更易于阅读
说明:当编译器无法推断类型或者推断的类型不准确时,就需要显式的传入类型参数
简化调用泛型函数和调用泛型函数有以下区别,当鼠标放上去,简化调用泛型函数
1.5 泛型约束
泛型约束:默认情况下,泛型函数的类型变量Type可以代表多个类型,这导致无法访问任何属性
比如id(‘a’)调用函数时获取参数的长度
function id<Type>(value:Type):Type {console.log(value.length)//这块length会爆红return value
}
解释:Type可以代表任意类型,无法保证一定纯在length属性,比如number类型就没有length
此时,就需要为泛型添加约束来收缩类型(缩窄类型取值范围)
添加泛型约束收缩类型,主要有以下两种方式:1. 制定更加具体的类型 2.添加约束
- 指定更加具体的类型
function id<Type>(value:Type[]):Type[] {
console.log(value.length)return value
}
比如,将类型修改为Type[] (Type类型的数组),因为只要是数组就一定存在length属性,因此就可以被访问了
//参数里面的Type不用改
function id<Type>(value:Type[]): Type[]{value.lengthreturn value
}
改变类型为type[]
为什么要添加泛型约束呢?
如果你不添加类型约束,咱们得类型变量Type呢就表示任意的类型的,因此就导致这个类型的变量去访问任何的属性,没法进行任何的操作
- 添加约束
interface Ilength {length:number}
function id<Type extends ILength>(value:Type):Type {//此处的extends就不代表继承了,代表要满足后面类型的要求console.log(value.length)return value
}
解释:
- 创建描述约束的接口ILength,该接口要求提供Length属性
- 通过extends关键字使用该接口,为泛型(类型变量)添加约束
- 该约束表示:传入的类型必须有Length属性
注意:传入的实参(比如,数组)只要有length属性即可,这也符合前面讲到的接口的类型兼容性
interface Ilength {length:number}
//此处的extends表示满足ILength里面的类型约束
function id<Type extends Ilength>(value:Type):Type {value.lengthreturn value
}
let num=id(['a','c'])
let num1=id("abcdf")
let num2=id({length:10,name:'jack'})
console.log(num,num1,num2)
//错误演示:id(123)
1.6 多个泛型变量的情况
泛型类型变量有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量的约束)
比如创建一个函数来获取对=对象中属性的值:
function getProp<Type,key extends keyof Type>(obj:Type,key:Key) {return obj[key]
}
let person = {name:'jack',age:18}
getProp(person,'name')
解释:
- 添加了第二个类型变量Key,两个类型变量之间使用(,)逗号分隔
- keyof关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型
- 本实例中keyof Type 实际上获取的是person对象所有键的联合类型,也就是:‘name’|‘age’
- 类型变量key受Type约束,可以理解为:key只能是Type所有键中的任意一个,或者说只能访问对象中存在的属性