从JavaScript到TypeScript泛型边城客栈

我们来模拟一个场景:某个服务提供了一些不同类型的数据,我们需要先通过一个中间件对这些数据进行一个基本的处理(比如验证,容错等),再对其进行使用。那么用JavaScript来写应该是这样的

//模拟服务,提供不同的数据。这里模拟了一个字符串和一个数值varservice={getStringValue:function(){return"astringvalue";},getNumberValue:function(){return20;}};//处理数据的中间件。这里用log来模拟处理,直接返回数据当作处理后的数据functionmiddleware(value){console.log(value);returnvalue;}//JS中对于类型并不关心,所以这里没什么问题varsValue=middleware(service.getStringValue());varnValue=middleware(service.getNumberValue());改写成TypeScript先来看看对服务的改写,TypeScript版的服务有返回类型:

constservice={getStringValue():string{return"astringvalue";},getNumberValue():number{return20;}};为了保证在对sValue和nValue的后续操作中类型检查有效,它们也会有类型(如果middleware类型定义得当,可以推导,这里我们先显示定义其类型)

constsValue:string=middleware(service.getStringValue());constnValue:number=middleware(service.getNumberValue());现在的问题是middleware要怎么样定义才既可能返回string,又可能返回number,而且还能被类型检查正确推导出来?

functionmiddleware(value:any):any{console.log(value);returnvalue;}是的,这个办法可以检查通过。但它的问题在于middleware内部失去了类型检查,在后在对sValue和nValue赋值的时候,也只是当作类型没有问题。简单的说,是有“假装”没问题。

functionmiddleware1(value:string):string{...}functionmiddleware2(value:number):number{...}当然也可以用TypeScript的重载(overload)来实现

functionmiddleware(value:string):string;functionmiddleware(value:number):number;functionmiddleware(value:any):any{//实现一样没有严格的类型检查}这种方法最主要的一个问题是……如果我有10种类型的数据,就需要定义10个函数(或重载),那20个,200个呢……

现在我们切入正题,用泛型来解决这个问题。那么这就需要解释一下什么是泛型了:泛型就是指定一个表示类型的变量,用它来代替某个实际的类型用于编程,而后通过实际调用时传入或推导的类型来对其进行替换,以达到一段使用泛型程序可以实际适应不同类型的目的。

虽然这个解释已经很接地气了,但是理解起来还是不如一个实例来得容易。我们来看看middleware的泛型实现是怎么样的

我们直接从VSCode的提示可以看出来,对于middleware()调用,TypeScript可以推导出参数类型和返回值类型:

我们也可以在调用的时候,小括号前显示指定T代替的类型,比如mdiddleware(...),不过如果指定的类型与推导的类型有冲突,就会提示错误:

相信大家都已经对数组有所了解,比如string[]表示字符串数组类型。其实在早期的TypeScript版本中没有这种数组类型表示,而是采用实例化的泛型Array来表示的,现在仍然可以使用这方式来表示数组。

除此之外,TypeScript中还有一个很常用的泛型类,Promise。因为Promise往往是带数据的,所以通过Promise这种泛型定义的形式,可以表示一个Promise所带数据的类型。比如下图就可以看出,TypeScript能正确推导出n的类型是number:

所以,泛型类其实多数时候是应用于容器类。假设我们需要实现一个FilteredList,我们可以向其中add()(添加)任意数据,但是它在添加的时候会自动过滤掉不符合条件的一些,最终通过getall()输出所有符合条件的数据(数组)。而过滤条件在构造对象的时候,以函数或Lambda表达式提供。

typePredicate=(v:T)=>boolean;classFilteredList{filter:Predicate;data:T[];constructor(filter:Predicate){...}add(value:T){...}getall():T[]{...}}当然类型变量也不一定非得叫T,也可以叫TValue或别的什么,但是一般建议以大写的T作为前缀,采用Pascal命名规则,方便识别。还有一些常见的指代,比如TKey表示键类型,TValue表示值类型等(常用于映射表这类容器定义)。

比如,我们有IAnimal这样一个接口,然后写一个run工具函数,它可以让动物跑起来,而且它会返回这个动物实例本身(以便链式调用)。先来定义类型

functionrun(animal:TAnimal):TAnimal{animal.run();//'run'doesnotexistontype'TAnimal'returnanimal;}采用这种定义,dog可以推导正确。不过由于TAnimal在这里只是个变量,可以代表任意类型,所以它并不能保证拥有run()方法可供调用。

正解是使用泛型约束,将TAnimal约束为实现了IAnimal。这需要在定义类型变量的使用使用extends来约束:

functionrun(animal:TAnimal):TAnimal{animal.run();//it'sokreturnanimal;}注意这里的语法,,虽然IAnimal是个接口,但这里不是在实现接口,extends表示约束关系,而非继承。它表示extends左边的类型变量实现了右边的类型,或者是右边类型的子孙类,或者就是右边的那个类型。简单的说,就是左边类型的实例可以赋值给右边类型的变量。

有时候我们希望传入某个工具方法的参数是一个类型,这样就可以通过new来生成对象。这在TypeScript中通常是使用构造函数来约束的,比如

functioncreate(type:{new():T}){returnnewtype();}constdog=create(Dog);这里约束了create可以创建动物的实例。如果不加extendsIAnimal,那么这个create可以创建任何类型的实例。

在使用泛型的时候,当然不会限制只使用一个类型变量,我们可以使用多个,比如可以这样定义一个Pair类

classPair{private_key:TKey;private_value:TValue;constructor(key:TKey,value:TValue){this._key=key;this._value=value;}getkey(){returnthis._key;}getvalue(){returnthis._value;}}其它应用自己定义泛型结构(泛型类或泛型函数)通常只会在写比较复杂的应用时发生。但是使用已定义好的泛型是极其常见的,上面已经提到了两个常见的泛型定义,T[]/Array和Promise,除此之外,还有ES6的Set和Map对应于TypeScript的泛型定义Set和Map。另外,泛型还常用于Generator和Iterable/Iterator:

THE END
1....1.定义一个描述宠物的抽象类Pet,包含重量(weight)和年龄(age...根据题意,利用面向对象程序设计的思想完成如下代码设计: 1.定义一个描述宠物的抽象类Pet,包含重量(weight)和年龄(age)两个成员变量和显示宠物资料的showInfo方法以及获取宠物资料的getInfo方法 ; 2.设计一个可吃的接口Eatable,包含一个被吃(beEatted)的方法 ; 3https://m.ppkao.com/wangke/daan/b979677cf11946dd8b8788209425c153
2.2023Web前端开发八股文&面试题(万字系列)——这篇就够了!代选择器的开销是最高的,尽量将选择器的深度降到最低,最高不要超过三层,更多的使用类来关联每一个标签元素。 了解哪些属性是可以通过继承而来的,然后避免对这些属性重复指定规则。 渲染性能: 属性值为0时,不加单位。 可以省略小数点之前的0。 标准化各种浏览器前缀:带浏览器前缀的在前。标准属性在后。 https://developer.aliyun.com/article/1353677
3.防水材料简介分类防水材料品种繁多,按其主要原料分为4类:①沥青类防水材料。以天然沥青、石油沥青和煤沥青为主要原材料,制成的沥青油毡、纸胎沥青油毡、溶剂型和水乳型沥青类或沥青橡胶类涂料、油膏,具有良好的粘结性、塑性、抗水性、防腐性和耐久性。②橡胶塑料类防水材料。以氯丁橡胶、丁基橡胶、三元乙丙橡胶、聚氯乙烯、聚...https://jijian.sdwu.edu.cn/info/1014/1027.htm
4.mysql经典面试题MySQL@下一站触发器是一段能自动执行的程序,是一种特殊的存储过程,触发器和普通的存储过程的区别是:触发器是当对某一个表进行操作时触发。诸如: update、 insert、 delete 这些操作的时候,系统会自动调用执行该表上对应的触发器。 SQL Server 2005 中触发器可以分为两类:DML 触发器和 DDL 触发器,其中 DDL 触发器它们会影...https://xie.infoq.cn/article/d1487934db6082b162810ddeb
5.面试题170. test.bns.com.cn 的域名是bns.com.cn ,如果要配置一域名服务器,应在named.conf 文件中定义DNS 数据库的工作目录。 71. Sendmail 邮件系统使用的两个主要协议是:_和 _ ,前者用来发送邮件,后者用来接收邮件。 72. DHCP 是动态主机配置协议的简称,其作用是:为网络中的主机_ 。 https://www.jianshu.com/p/c2ee8bde5210
1.深度刨析“类”类是一种数据类型(引用类型),具体到每一个类上,每一个类都是自定义类型 比如具体到学生类上,它是我们自定义的类。 可以用类声明变量、创造实例(类当成实例的模板)。这就体现了它是数据类型 类代表现实世界的“种类” 类的静态成员,体现的就是类这个种类的特征。比如自定义学生类中静态成员total表示学生总人数...https://blog.csdn.net/m0_65804432/article/details/143767019
2.泛型类其中一个有用的规则是,应用最大程度的约束,同时仍可处理必须处理的类型。 例如,如果知道泛型类仅用于引用类型,则请应用类约束。 这可防止将类意外用于值类型,并使你可在T上使用as运算符和检查 null 值。 是否将泛型行为分解为基类和子类。 因为泛型类可用作基类,所以非泛型类的相同设计注意事项在此也适用。 请...https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/generic-classes
3.java猫狗实验mob64ca12d52440的技术博客步骤1:定义Cat类 首先,我们要定义一个Cat类,用于表示猫的属性和行为。我们可以为猫定义属性,如名字、年龄和颜色,并定义方法,如叫声和行走。 publicclassCat{privateStringname;privateintage;privateStringcolor;publicCat(Stringname,intage,Stringcolor){this.name=name;this.age=age;this.color=color;}publicvoidmeow...https://blog.51cto.com/u_16213319/8774007
4.类型断言·TypeScript入门教程可是swim 函数接受的参数是 Cat | Fish,一旦传入的参数是 Cat 类型的变量,由于 Cat 上没有 swim 方法,就会导致运行时错误了。总之,使用类型断言时一定要格外小心,尽量避免断言后调用方法或引用深层属性,以减少不必要的运行时错误。将一个父类断言为更加具体的子类§当类之间有继承关系时,类型断言也是很常见的:...https://ts.xcatliu.com/basics/type-assertion.html
5.2023年初中英语阅读理解专题(二)英语阅读(一)根据定义或解释猜测词义 A bag is useful and the word “bag” is useful. It gives us some interesting phrases(短语). One is “ to let the cat out of the bag”. It is the same as “to tell a secret”…. Now when someone lets out (泄漏) a secret, he “lets the cat out ...http://www.zhongkao.com/e/20230331/6426c7d83a06d.shtml
6.前端200道面试题及答案(更新中)添加相同的样式,如果想要修改一种样式,又不得不修改所有的 style 中的代码。很显然,内联方式引入 CSS 代码会导致 HTML 代码变得冗长,且使得网页难以维护。 嵌入样式 嵌入方式也称页级CSS或内部CSS,整体是放在head标签里边的,在style标签里边定义样式,作用范围和字面意思相同,仅限于本页面的元素;如果你写的代码超过了...https://leheavengame.com/article/64d7982931f3516f1405dd03
7.自考C++程序设计2016年4月试题自考7.类Cat是类Animal的公有派生类,类Animal和类Cat中都定义了虚函数func( ),p是一个指向类Animal对象的指针,则p-﹥Animal:: func( )将( ) A.调用类Animal中的函数func( ) B.调用类Cat中的函数func( ) C.根据p所指的对象类型而确定调用类Animal中或类Cat中的函数func( ) ...https://www.educity.cn/zikao/26423.html
8.成功斩获腾讯offer,分享我的面试经历(附书籍推荐,资料分享)可以,而且如果说这个类不是final的,也就是说他是某一个类的父类,那么该类的析构函数必须是虚函数,因为如果不是虚函数,那么其子类对象的父类组成部分将无法得到释放,造成资源泄露。 8、析构函数可不可以是纯虚函数? 我觉得不建议是,因为我们知道纯虚函数是没有实现体的,那么子类对象在析构的时候,父类组成部分...https://maimai.cn/article/detail?fid=1367118499&efid=dxTzJOD7aSelu7sneqtM2g
9.计算机二级C语言考试冲刺练习题A、C语言程序总是从第一个定义的函数开始执行 B、在C语言程序中,要调用的函数必须在main( )函数中定义 C、C语言程序总是从main( )函数开始执行 D、C语言程序中的main( )函数必须放在程序的开始部分 4.下列关于C语言的说法错误的是( B ) 。 A、 C程序的工作过程是编辑、编译、连接、运行 ...https://www.oh100.com/kaoshi/ncre2/tiku/538366.html
10.SpringBoot部署与服务配置方式java2、Java类中@Profile注解 下面2个不同的类实现了同一个接口,@Profile注解指定了具体环境 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 // 接口定义 public interface SendMessag...https://www.jb51.net/program/322041ntd.htm
11.价差700你选谁?魅族MX4/MX4Pro对比(全文)魅族MX4其次是锁屏自定义右滑,在Flyme 4.0中也有这一功能,不过是要在锁屏界面右滑然后选择相应功能点击进入的。在Flyme 4.1中用户直接在设置菜单里选中想实现的功能,然后锁屏状态下右滑就直接进入,方便很多。而Flyme 4.1也加入了语音唤醒,开启功能后对着手机说“你好魅族”就可以直接唤醒手机。 https://mobile.zol.com.cn/493/4931233_all.html