一.什么是泛型
什么是泛型?我们可以理解为是在编译期间不确定方法的类型,在方法调用时再指定类型的一种特性。这句话又怎么理解呢?在TS中,定义变量最重要的就是对类型的约束,但是当需要定义一个参数类型 返回 值类型不确定的函数时,这时就可能会使用泛型。
这时有人会说为什么不使用any类型来定义呢?还要弄一个泛型出来?这对于写高复用性的第三方库和第三方源码的时候,泛型的应用就非常重要了。(比如用any来定义,最后使用者把鼠标指上去后,发现全部是any,就问你懵不懵?)
二.泛型的使用
先来看看开头我说的使用any作为类型来定义一个函数
//参数类型和返回值类型都为any的函数
test(name:any):any{
return name
}
test("泛型");
使用 any 定时存在问题:虽然可以知道传入值的类型但是无法获取函数返回值的类型 。
我们在传参的时候可以看出我们传的是String类型的参数,但是返回的a还是any,这就说明类型丢失了。
但是我们想传入的参数和返回的参数的类型保持一致,这个时候就要用到泛型了。
//定义一个函数泛型
test<T>(name:T):T{
return name;
}
test<string>("泛型") //调用 传入string类型,此时函数的返回值的类型是string
test<number>(123) //调用 传入number类型,此时函数的返回值的类型是number
//隐式推断
test("泛型")
//如果不传入<T>, 默认将第一个参数的数据类型作为泛型指定的类型
//这时候也就相当于test<string>("泛型")
上面的代码简单的定义了一个泛型的函数,使用了<>。定义的时候使用<>把类型变量包起来,T可以是任意值。也就相当于函数中的变量,只不过是将类型作为了变量放在<>中。
三.接口泛型
//在这个函数泛型中 如果我们想访问info这个变量的name属性,是会报错的。name属性不存在类型T中
test<T>(info:T):T{
return info.name //报错
}
如果要想访问 name 属性,这时候就要使用接口来约束这个泛型。定义一个接口约束,使用 extends 关键字来实现。简单来说就是你定义一个类型,然后让 T 实现这个接口即可。
interface InfoData{
name:string
}
//此时相当于约束了泛型的类型必须是含有 name 属性的数据类型
test<T extends InfoData>(info:T):T{
return info.name //此时可以访问 name 属性
}
当我们使用泛型时,在代码中访问被泛型类型的变量时,容易出现各种各样的报错,这个时候我们需要对泛型进行约束,告知代码被赋予泛型类型的变量拥有些什么。
四.泛型类
泛型类可以支持不特定的数据类型,要求传入的参数和返回的参数必须一致,T表示泛型,具体什么类型是调用这个方法的时候决定的。
//定义一个MinVal类来实现最小值的获取
class MinVal<T>{
list: T[] = [];
add(value: T): void {
this.list.push(value);
}
min():T{
var min=this.list[0];
this.arr.forEach((value)=> {
if(value<min){
min=value;
}
});
return min;
}
}
//实例化类并且制定了类的T代表的类型是number
var m1 = new MinClas<number>();
m1.add(8);
m1.add(20);
m1.add(1);
console.log(m1.min()); //1
//实例化类并且制定了类的T代表的类型是string
var m2 = new MinClas<string>();
m2.add('c');
m2.add('e');
m2.add('l');
console.log(m2.min()); //c
这里只简单的写了一些泛型的基本定义和使用,还有很多复杂的灵活运用要在实际应用中摸索,泛型在大佬的面前使用起来五花八门,需要继续学习。