【前端】你好,我叫TypeScript 03──数据类型

泛型

写在前面

今天是520,祝大家有情人终成眷属吧​,咱们也应应景​,解锁两个新英雄:蹦蹦和跳跳,探索以全新的问答视角来介绍和解决亟需处理的问题。

什么是泛型?

蹦蹦:跳跳,你晓得啥子是泛型嘛?

跳跳:在历史中,强者高手都是考虑现在和未来的,只有宏观把握才能让自己立于不败之地。就像你耍游戏撒,不管你要出啥子大件装备,你先买一双孩子或者多兰剑、蓝色戒指你就不会错撒,反正都是要在这个方面拓展。

而在面向对象的编程语言中,组件不仅要考虑到现在的数据类型,还要考虑到未来要使用的数据类型,而泛型完美的提供了组件的可复用性,提高组件的灵活性。晓得咯不?

为什么要设计泛型?

蹦蹦:跳跳,那么为什么要设计泛型撒?

跳跳:设计泛型的目的就是在成员之间提供有意义的约束,而这些成员包括:类的实例成员、类的方法、函数参数和函数返回值。

跳跳JS老哥他是作为一门动态语言存在的,存在很大的灵活性,只有在执行过程中变量赋值了,你才晓得这个变量是啥子类型的。那么这就存在一个隐患,我不晓得要事先赋值啥子类型的变量,那么在执行过程中就会存在类型不对的错误,这就降低了代码的可维护性。而TS中有三种方法可以解决JS的可复用性差、可维护性差等问题,那么我们来看看是哪些:

  1. 函数重载
function getVal(val: number): number 
function getVal(val: string):string 
function getVal(val: any):any {
   return val;
}
  1. 联合类型
function getVal(val: string | number | any[]):string | number | any[] {
  return val;
}

在追求代码的简洁可读的时代,你写这又臭又长的代码是几个意思,有点瓜兮兮。而TS小老弟还是会来事,提前考虑到了这些问题,提供泛型方式来解决这些问题。

  1. 泛型
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中文文档》

写在最后

我是前端小菜鸡,感谢大家的阅读,我将继续和大家分享更多优秀的文章,此文参考了大量书籍和文章,如果有错误和纰漏,希望能给予指正。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值