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

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

[[400582]]

泛型

写在前面

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

什么是泛型?

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

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

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

为什么要设计泛型?

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

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

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

函数重载

  1. function getVal(val: number): number  
  2. function getVal(val: string):string  
  3. function getVal(val: any):any { 
  4.    return val; 

联合类型

  1. function getVal(val: string | number | any[]):string | number | any[] { 
  2.   return val; 

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

泛型

  1. function getVal<T>(val: T): T { 
  2.    return val; 

解释哈泛型的规则,上面的「T表示的是待捕获函数传入参数类型(Type),在函数内部使用T可用于该参数类型声明其他变量」。实际上T并不是固定的,可以用任何有效名称替代。比如:

  • K(Key):表示对象中的键类型
  • V(Value):表示对象中的值类型
  • E(Element):表示元素的类型

如何使用泛型?「蹦蹦」:那么愣个使用泛型撒?

「跳跳」:愣个使用泛型,系好安全带,我们要出发了。

我们看到,当我们调⽤ identity(1), Number类型就像参数 1 ⼀样,它将在出现T的任何位置填充该类型。图中内部的 T 被称为类型变量,它是我们希望传递给 identity 函数的类型占位符,同时它被分配给 value 参数⽤来代替它的类型:此时 T充当的是类型,⽽不是特定的Number 类型。

「蹦蹦」:讲的还是挺详细的哈,那么那个<>里面可以写两个变量类型不?

「跳跳」:这个肯定阔以撒,哪怕你两个,两百个都阔以。我就举个栗子撒,我们阔以看到下面的代码中,用了两个类型变量⽤于扩展我们定义的 identity 函数:

除了为类型变量显式设定值之外,⼀种更常⻅的做法是使编译器⾃动选择这些类型,从⽽使代码更简洁。我们可以完全省略尖括号,利用了类型推论──即编译器会根据传入的参数自动地帮助我们确定T的类,类型推论帮助我们保持代码精简和高可读性。

  1. let output = identity<string>("myString");  // type of output will be 'string' 
  2.  
  3. let output = identity("myString");  // type of output will be 'string' 

泛型接口和泛型类

「蹦蹦」:跳跳,我昨天看到「一川」写的关于接口的文章,看到有对象接口、类接口啥的,泛型是不是也有相关的概念。

「跳跳」:不错哈,都会进行抢答了。我们先看看泛型接口:

  1. interface  FanxingInter<T>{ 
  2.   //定义了一个非泛型函数签名作为泛型类型的一部分 
  3.   (name:T):T; 
  4.  
  5. function func<T>(name:T):T{ 
  6.   return name
  7.  
  8. // 在使用泛型接口时,需要传入一个类型参数来指定泛型类型(这里是number),锁定了之后代码里使用的类型 
  9. let fxFun: FanxingInter<string> = func; 
  10. fxFun("yichuan"); 

我们再看看,泛型如何在类中进行使用,定义泛型类的。

其实,泛型类看上去与泛型接口差不多。泛型类使用(<>)括起泛型类型,跟在类名后面,用于定义任意多个类型变量。

  1. interface FxInterface<T>{ 
  2.   value:T; 
  3.   getValue:()=>T; 
  4.  
  5. class FxClass<T> implements FxInterface<T>{ 
  6.   value:T; 
  7.   constructor(value:T){ 
  8.     this.value = value; 
  9.   } 
  10.   getValue():T{ 
  11.     return this.value; 
  12.   } 
  13.  
  14. const myFxClass = new FxClass<string>("yichuan"); 
  15. console.log(myFxClass.getValue()); 

调用过程:

  • 先实例化对象FxClass,传入string类型的参数值"yichuan";
  • 在FxClass类中,类型变量T的值变成string类型;
  • FxClass类实现了FxInterface,此时T表示的是string类型,即实现了泛型接口;

我们归纳一下,就是:

  1. function Func<T>(){} //泛型函数,尖括号跟在函数名后 
  2. class Dog<T>{} //泛型类,尖括号跟在类名后 
  3. interface NiuInterface<T>{} //泛型接口,尖括号跟随接口名后 

「跳跳」:蹦蹦,泛型接口和泛型类,懂了不。

泛型约束

「蹦蹦」:懂了。我突然想到一个问题,如果想要去操作某类型对应的某些属性,比如说访问参数name的length,编译器为什么会报错?

  1. function nameFunc<T>(name:T):T{ 
  2.   console.log(name.length);//Error 
  3.   return name

「跳跳」:这个问题挺不错了,说明你对泛型整挺好哈。我们看到,因为编译器也不知道你输入的参数类型T是否具有length属性,要解决它可以让那个类型变量extends含有所需属性的接口。

  1. interface Length{ 
  2.   length: number; 
  3.  
  4. function idenLength<T extends Length>(name: T):T{ 
  5.   console.log(name.length); 
  6.   return name

T extends Length是对泛型的约束,告诉编译器已经支持Length接口的任何类型。对于使用不含length属性的对象作为参数调用函数时,ts会提示相应的错误信息:

  1. console.log(idenLength(18));//Error 

此外,我们还可以使⽤ , 号来分隔多种约束类型,⽐如: 。⽽对于上述的 length 属性问题来说,如果我们显式地将变量设置为数组类型,也可以解决该问题。

高端玩家──索引类型

「蹦蹦」:泛型中如何检查对象的键是否存在呢?

「跳跳」:其实也很简单,同样使用泛型约束进行检测。只不过需要通过索引类型查询操作符:keyof,用于获取某种类型T所有的键,返回类型的公共属性的联合类型。

  1. interface Person{ 
  2.   name:string; 
  3.   age:number; 
  4. let personProps: keyof Person;//"name" | "age" 

我们就可以结合前⾯介绍的 extends 约束,即限制输⼊的属性名包含在 keyof 返回的联合类型中。

  1. function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { 
  2.   return obj[key]; 

在以上的 getProperty 函数中,我们通过 K extends keyof T确保参数 key⼀定是对象中含有的键,这样就不会发⽣运⾏时错误。

  1. class BeeKeeper { 
  2.     hasMask: boolean; 
  3.  
  4. class ZooKeeper { 
  5.     nametag: string; 
  6.  
  7. class Animal { 
  8.     numLegs: number; 
  9.  
  10. class Bee extends Animal { 
  11.     keeper: BeeKeeper; 
  12.  
  13. class Lion extends Animal { 
  14.     keeper: ZooKeeper; 
  15.  
  16. function createInstance<A extends Animal>(c: new () => A): A { 
  17.     return new c(); 
  18.  
  19. createInstance(Lion).keeper.nametag;  // typechecks! 
  20. createInstance(Bee).keeper.hasMask;   // typechecks! 

「蹦蹦」:懂了,懂了,就是有点复杂,得去捋一捋。

小结

「跳跳」:其实本篇文章主要介绍了:泛型的概念、泛型接口和泛型类、泛型约束以及索引类型等等。

参考文章

  • 阿宝哥的《重学TS》
  • 《ts中文文档》

本文转载自微信公众号「前端万有引力」,可以通过以下二维码关注。转载本文请联系前端万有引力众号。

 

责任编辑:姜华 来源: 前端万有引力
相关推荐

2021-05-18 07:37:18

前端TypeScript数据类型

2021-06-17 09:32:17

前端TypeScript 技术热点

2021-05-19 07:35:53

TypeScript变量和接口前端

2021-05-25 07:39:18

TypeScript 前端函数与类

2021-10-14 14:00:44

996加班工作

2016-08-18 14:13:55

JavaScript基本数据引用数据

2014-01-05 17:08:09

PostgreSQL数据类型

2019-08-12 11:40:48

数据库SQLite3数据类型

2010-07-22 17:57:40

2021-02-20 13:55:35

程序员计算机技术

2021-03-12 08:02:34

Redis数据类型.

2017-07-10 13:38:07

MySQL数据类型整数类型

2013-07-30 14:48:58

.NET数据类型

2013-07-30 14:00:46

.NET数据类型

2010-10-15 13:28:34

MySql数据类型

2010-08-10 17:17:59

2022-03-07 05:53:41

线程CPU代码

2010-08-11 09:14:33

DB2数据类型

2010-10-08 15:11:28

JavaScript数

2010-09-17 14:49:04

Java数据类型
点赞
收藏

51CTO技术栈公众号