泛型
写在前面
今天是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
「蹦蹦」:讲的还是挺详细的哈,那么那个<>里面可以写两个变量类型不?
「跳跳」:这个肯定阔以撒,哪怕你两个,两百个都阔以。我就举个栗子撒,我们阔以看到下面的代码中,用了两个类型变量⽤于扩展我们定义的 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表示的是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
此外,我们还可以使⽤ , 号来分隔多种约束类型,⽐如:
高端玩家──索引类型
「蹦蹦」:泛型中如何检查对象的键是否存在呢?
「跳跳」:其实也很简单,同样使用泛型约束进行检测。只不过需要通过索引类型查询操作符: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中文文档》
本文转载自微信公众号「前端万有引力」,可以通过以下二维码关注。转载本文请联系前端万有引力众号。