本篇文章主要介绍 typeScript 中新增的泛型概念、泛型使用、泛型与接口结合等内容。
在实际应用中可能会遇到求最小值的问题,比如求数组中的最小值。
在 ts 中的就需要写两种方式,一种针对 number,另外一种针对字符串。
这样写不利于代码重用,项目较大时,性能较差,同时工作效率也低,所以在 ts 中引入了泛型概念。
function getMin1(arr:number[]):number {
let min = arr[0]
for (var i = 1; i < arr.length; i++){
if (min > arr[i]) {
min = arr[i]
}
}
return min
}
console.log(getMin1([1, 2, 3, 4]));
function getMin2(arr:string[]):string {
let min = arr[0]
for (var i = 1; i < arr.length; i++){
if (min > arr[i]) {
min = arr[i]
}
}
return min
}
console.log(getMin2(['a', 'b', 'c']));
1、泛型是啥?
泛型英文是 generics ,是指在定义函数、接口或类的时候,不预先指定具体的类型,而是在使用的时候再指定类型的一种。
定义方式:
function fnName<T>(arg:T,...):T{
return ...
}
泛型变量通常用 T 来表示,T可以表示任何类型。
所以呢,我们可以将上述实例修改成以下代码:
function getMin<T>(arr: T[]): T{
let min = arr[0]
for (var i = 1; i < arr.length; i++){
if (min > arr[i]) {
min = arr[i]
}
}
return min
}
getMin<number>([1, 2, 3, 4])
getMin<string>(['a', 'b', 'c', 'd'])
上述代码中,T 的主要作用就是帮助我们来捕获用户传入的类型,比如 :number 或 string 。另外编译器也会根据传入的参数自动地帮助我们进行类型推断,然后把 T 设置为它的类型,所以可以忽略类型的传入,如:
getMin([1, 2, 3, 4])
getMin(['a', 'b', 'c', 'd'])
在一些复杂的情况下,为了防止编译器自动推断类似失败,尽可能地将类型传入,防止出错。
2、泛型类型
泛型函数的类型和非泛型函数的类型有什么不同?
它们看着很相似,泛型函数类型前面有一个类型参数 。
对于泛型函数类型还有以下特性:
a>、泛型函数类型可以有多个参数
function fn<T, U>(arg1: T, arg2: U) {
return arg1
}
b>、泛型函数可以使用不同的泛型参数名
function fn<T>(arg1: T) {
return arg1
}
let Fn: <M>(arg1: M) => M = fn
c>、可以使用带有对象字面量的方式定义泛型函数
function fn<T>(arg1: T) {
return arg1
}
let Fn: {<T>(arg: T): T} = fn
3、泛型接口
在使用对象字面量的方式定义泛型函数时,对象的形式可以替换成接口的形式,改为:
let Fn: { <T>(arg: T): T } = fn
//替换为
interface FnInter{
<T>(arg: T): T
}
let Fn: FnInter = fn
这种方式存在问题:函数对数据类型一无所知,无法使用某个数据类型进行操作。所以需要改良下,将类型作为参数传入,如:
interface FnInter<T>{
(arg:T): T
}
let Fn: FnInter<string> = fn
这样我们就能清楚地知道使用的具体是那个泛型类型。
我们将整个接口当做泛型参数,就叫做泛型接口。它的优点就是我们能清除知道参数的数据类型,接口内的成员也能知道参数的具体类型。
4、泛型类
除了有泛型接口之外,还有接口类。泛型类与泛型函数差不多。
语法格式为:
class 名称<T>{}
new 类名<类型>()
class GetMin<T>{
arr: T[] = []
add(ele:T) {
this.arr.push(ele)
}
getMin(): T{
var min = this.arr[0]
for (var i = 0; i < this.arr.length; i++){
if (min > this.arr[i]) {
min = this.arr[i]
}
}
return min
}
}
let gMin1 = new GetMin<number>()
gMin1.add(1)
gMin1.add(5)
console.log(gMin1.getMin());//1
let gMin2 = new GetMin<string>()
gMin2.add('a')
gMin2.add('b')
console.log(gMin2.getMin());//2
5、泛型约束
泛型功能确实挺强大的,但它也不是万能的。比如:
function getLength<T>(val: T): number {
return val.length;
}
错误信息提示:类型“T”上不存在属性“length”。
原因是只有字符串、数组才有 length 属性,对于数字、对象没有 length 属性,所以报错了。解决办法是要保证传入的数据类型有 length 属性,所以需要使用泛型约束。
泛型约束主要是通过接口 + extends 关键字来实现约束。
interface ILen{
length:number
}
function getLength<T extends ILen>(val: T): number {
return val.length;
}
console.log(getLength<string>("abcd"));
console.log(getLength<number>(1)); //错误提示:类型“number”不满足约束“ILen”。
使用泛型约束的优点是帮我们自动检测传入的值是否符合约束类型的值,不满足时就会有错误提示。
6、泛型参数默认类型
在 typeScript 2.3 以后,可以为泛型中的类型参数指定默认类型,当使用泛型时没有指定参数类型,并且编辑器从实际参数中也无法推断出数据类型时,就使用默认类型。
使用简单:
interface P<T = string>{
name:T
}
let p1: P = { name: "小姐姐" }
let p2: P<number> = { name: 18 }
泛型参数的默认类型遵循以下规则:
- 有默认类型的类型参数被认为是可选的。
- 必选的类型参数不能在可选的类型参数后。
- 如果类型参数有约束,类型参数的默认类型必须满足这个约束。
- 当指定类型实参时,你只需要指定必选类型参数的类型实参。 未指定的类型参数会被解析为它们的默认类型。
- 如果指定了默认类型,且类型推断无法选择一个候选类型,那么将使用默认类型作为推断结果。
- 一个被现有类或接口合并的类或者接口的声明可以为现有类型参数引入默认类型。
- 一个被现有类或接口合并的类或者接口的声明可以引入新的类型参数,只要它指定了默认类型。
7、泛型条件类型
在 typeScript 2.8 中,引入了条件类型,我们可以根据某些条件得到不同的类型,此处的条件是类型兼容性约束。
条件类型会以一定条件表达式进行类型关系检测,从而在两种类型中选择其一:
使用语法:
T extends U ? X : Y
以上表达式的意思是:若 T 能够赋值给 U,那么类型是 X,否则为 Y。