泛型
写在前面
今天是520,祝大家有情人终成眷属吧,咱们也应应景,解锁两个新英雄:蹦蹦和跳跳,探索以全新的问答视角来介绍和解决亟需处理的问题。
什么是泛型?
蹦蹦:跳跳,你晓得啥子是泛型嘛?
跳跳:在历史中,强者高手都是考虑现在和未来的,只有宏观把握才能让自己立于不败之地。就像你耍游戏撒,不管你要出啥子大件装备,你先买一双孩子或者多兰剑、蓝色戒指你就不会错撒,反正都是要在这个方面拓展。
而在面向对象的编程语言中,组件不仅要考虑到现在的数据类型,还要考虑到未来要使用的数据类型,而泛型完美的提供了组件的可复用性,提高组件的灵活性。晓得咯不?
为什么要设计泛型?
蹦蹦:跳跳,那么为什么要设计泛型撒?
跳跳:设计泛型的目的就是在成员之间提供有意义的约束,而这些成员包括:类的实例成员、类的方法、函数参数和函数返回值。
跳跳:JS
老哥他是作为一门动态语言存在的,存在很大的灵活性,只有在执行过程中变量赋值了,你才晓得这个变量是啥子类型的。那么这就存在一个隐患,我不晓得要事先赋值啥子类型的变量,那么在执行过程中就会存在类型不对的错误,这就降低了代码的可维护性。而TS中有三种方法可以解决JS的可复用性差、可维护性差等问题,那么我们来看看是哪些:
- 函数重载
function getVal(val: number): number
function getVal(val: string):string
function getVal(val: any):any {
return val;
}
- 联合类型
function getVal(val: string | number | any[]):string | number | any[] {
return val;
}
在追求代码的简洁可读的时代,你写这又臭又长的代码是几个意思,有点瓜兮兮。而TS小老弟还是会来事,提前考虑到了这些问题,提供泛型方式来解决这些问题。
- 泛型
function getVal<T>(val: T): T {
return val;
}
解释哈泛型的规则,上面的T表示的是待捕获函数传入参数类型(Type),在函数内部使用T可用于该参数类型声明其他变量。实际上T并不是固定的,可以用任何有效名称替代。比如:
- K(Key):表示对象中的键类型
- V(Value):表示对象中的值类型
- E(Element):表示元素的类型
如何使用泛型?
蹦蹦:那么愣个使用泛型撒?
跳跳:愣个使用泛型,系好安全带,我们要出发了。
我们看到,当我们调⽤ identity<Number>(1)
, Number
类型就像参数 1 ⼀样,它将在出现T
的任何位置填充该类型。图中<T>
内部的 T
被称为类型变量,它是我们希望传递给 identity
函数的类型占位符,同时它被分配给 value
参数⽤来代替它的类型:此时 T
充当的是类型,⽽不是特定的Number
类型。
蹦蹦:讲的还是挺详细的哈,那么那个<>里面可以写两个变量类型不?
跳跳:这个肯定阔以撒,哪怕你两个,两百个都阔以。我就举个栗子撒,我们阔以看到下面的代码中,用了两个类型变量⽤于扩展我们定义的 identity
函数:
除了为类型变量显式设定值之外,⼀种更常⻅的做法是使编译器⾃动选择这些类型,从⽽使代码更简洁。我们可以完全省略尖括号,利用了类型推论──即编译器会根据传入的参数自动地帮助我们确定T
的类,类型推论帮助我们保持代码精简和高可读性。
let output = identity<string>("myString"); // type of output will be 'string'
let output = identity("myString"); // type of output will be 'string'
泛型接口和泛型类
蹦蹦:跳跳,我昨天看到一川写的关于接口的文章,看到有对象接口、类接口啥的,泛型是不是也有相关的概念。
跳跳:不错哈,都会进行抢答了。我们先看看泛型接口:
interface FanxingInter<T>{
//定义了一个非泛型函数签名作为泛型类型的一部分
(name:T):T;
}
function func<T>(name:T):T{
return name;
}
// 在使用泛型接口时,需要传入一个类型参数来指定泛型类型(这里是number),锁定了之后代码里使用的类型
let fxFun: FanxingInter<string> = func;
fxFun("yichuan");
我们再看看,泛型如何在类中进行使用,定义泛型类的。
其实,泛型类看上去与泛型接口差不多。泛型类使用(<>)括起泛型类型,跟在类名后面,用于定义任意多个类型变量。
interface FxInterface<T>{
value:T;
getValue:()=>T;
}
class FxClass<T> implements FxInterface<T>{
value:T;
constructor(value:T){
this.value = value;
}
getValue():T{
return this.value;
}
}
const myFxClass = new FxClass<string>("yichuan");
console.log(myFxClass.getValue());
调用过程:
- 先实例化对象
FxClass
,传入string
类型的参数值"yichuan"
; - 在
FxClass
类中,类型变量T的值变成string类型; FxClass
类实现了FxInterface<T>
,此时T表示的是string类型,即实现了泛型接口;
我们归纳一下,就是:
function Func<T>(){} //泛型函数,尖括号跟在函数名后
class Dog<T>{} //泛型类,尖括号跟在类名后
interface NiuInterface<T>{} //泛型接口,尖括号跟随接口名后
跳跳:蹦蹦,泛型接口和泛型类,懂了不。
泛型约束
蹦蹦:懂了。我突然想到一个问题,如果想要去操作某类型对应的某些属性,比如说访问参数name的length,编译器为什么会报错?
function nameFunc<T>(name:T):T{
console.log(name.length);//Error
return name;
}
跳跳:这个问题挺不错了,说明你对泛型整挺好哈。我们看到,因为编译器也不知道你输入的参数类型T是否具有length
属性,要解决它可以让那个类型变量extends
含有所需属性的接口。
interface Length{
length: number;
}
function idenLength<T extends Length>(name: T):T{
console.log(name.length);
return name;
}
T extends Length
是对泛型的约束,告诉编译器已经支持Length
接口的任何类型。对于使用不含length属性的对象作为参数调用函数时,ts会提示相应的错误信息:
console.log(idenLength(18));//Error
此外,我们还可以使⽤ , 号来分隔多种约束类型,⽐如:<T extends Length, Type2, Type3>
。⽽对于上述的 length
属性问题来说,如果我们显式地将变量设置为数组类型,也可以解决该问题。
高端玩家──索引类型
蹦蹦:泛型中如何检查对象的键是否存在呢?
跳跳:其实也很简单,同样使用泛型约束进行检测。只不过需要通过索引类型查询操作符:keyof
,用于获取某种类型T所有的键,返回类型的公共属性的联合类型。
interface Person{
name:string;
age:number;
}
let personProps: keyof Person;//"name" | "age"
我们就可以结合前⾯介绍的 extends
约束,即限制输⼊的属性名包含在 keyof
返回的联合类型中。
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
在以上的 getProperty
函数中,我们通过 K extends keyof T
确保参数 key
⼀定是对象中含有的键,这样就不会发⽣运⾏时错误。
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!
蹦蹦: 懂了,懂了,就是有点复杂,得去捋一捋。
小结
跳跳:其实本篇文章主要介绍了:泛型的概念、泛型接口和泛型类、泛型约束以及索引类型等等。
参考文章
- 阿宝哥的《重学TS》
- 《ts中文文档》
写在最后
我是前端小菜鸡,感谢大家的阅读,我将继续和大家分享更多优秀的文章,此文参考了大量书籍和文章,如果有错误和纰漏,希望能给予指正。