你不知道的 TypeScript 高级类型(小结)
前言
对于有JavaScript基础的同学来说,入门TypeScript其实很容易,只需要简单掌握其基础的类型系统就可以逐步将JS应用过渡到TS应用。
//js constdouble=(num)=>2*num //ts constdouble=(num:number):number=>2*num
然而,当应用越来越复杂,我们很容易把一些变量设置为any类型,TypeScript写着写着也就成了AnyScript。为了让大家能更加深入的了解TypeScript的类型系统,本文将重点介绍其高级类型,帮助大家摆脱AnyScript。
泛型
在讲解高级类型之前,我们需要先简单理解泛型是什么。
泛型是强类型语言中比较重要的一个概念,合理的使用泛型可以提升代码的可复用性,让系统更加灵活。下面是维基百科对泛型的描述:
泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。
泛型通过一对尖括号来表示(<>),尖括号内的字符被称为类型变量,这个变量用来表示类型。
functioncopy(arg:T):T{ if(typeofarg==='object'){ returnJSON.parse( JSON.stringify(arg) ) }else{ returnarg } }
这个类型T,在没有调用copy函数的时候并不确定,只有调用copy的时候,我们才知道T具体代表什么类型。
conststr=copy('mynameistypescript')
我们在VSCode中可以看到copy函数的参数以及返回值已经有了类型,也就是说我们调用copy函数的时候,给类型变量T赋值了string。其实,我们在调用copy的时候可以省略尖括号,通过TS的类型推导是可以确定T为string的。
高级类型
除了string、number、boolean这种基础类型外,我们还应该了解一些类型声明中的一些高级用法。
交叉类型(&)
交叉类型说简单点就是将多个类型合并成一个类型,个人感觉叫做「合并类型」更合理一点,其语法规则和逻辑“与”的符号一致。
T&U
假如,我现在有两个类,一个按钮,一个超链接,现在我需要一个带有超链接的按钮,就可以使用交叉类型来实现。
interfaceButton{ type:string text:string } interfaceLink{ alt:string href:string } constlinkBtn:Button&Link={ type:'danger', text:'跳转到百度', alt:'跳转到百度', href:'http://www.baidu.com' }
联合类型(|)
联合类型的语法规则和逻辑“或”的符号一致,表示其类型为连接的多个类型中的任意一个。
T|U
例如,之前的Button组件,我们的type属性只能指定固定的几种字符串。
interfaceButton{ type:'default'|'primary'|'danger' text:string } constbtn:Button={ type:'primary', text:'按钮' }
类型别名(type)
前面提到的交叉类型与联合类型如果有多个地方需要使用,就需要通过类型别名的方式,给这两种类型声明一个别名。类型别名与声明变量的语法类似,只需要把const、let换成type关键字即可。
typeAlias=T|U
typeInnerType='default'|'primary'|'danger' interfaceButton{ type:InnerType text:string } interfaceAlert{ type:ButtonType text:string }
类型索引(keyof)
keyof类似于Object.keys,用于获取一个接口中Key的联合类型。
interfaceButton{ type:string text:string } typeButtonKeys=keyofButton //等效于 typeButtonKeys="type"|"text"
还是拿之前的Button类来举例,Button的type类型来自于另一个类ButtonTypes,按照之前的写法,每次ButtonTypes更新都需要修改Button类,如果我们使用keyof就不会有这个烦恼。
interfaceButtonStyle{ color:string background:string } interfaceButtonTypes{ default:ButtonStyle primary:ButtonStyle danger:ButtonStyle } interfaceButton{ type:'default'|'primary'|'danger' text:string } //使用keyof后,ButtonTypes修改后,type类型会自动修改 interfaceButton{ type:keyofButtonTypes text:string }
类型约束(extends)
这里的extends关键词不同于在class后使用extends的继承作用,泛型内使用的主要作用是对泛型加以约束。我们用我们前面写过的copy方法再举个例子:
typeBaseType=string|number|boolean //这里表示copy的参数 //只能是字符串、数字、布尔这几种基础类型 functioncopy(arg:T):T{ returnarg }
如果我们传入一个对象就会有问题。
extends经常与keyof一起使用,例如我们有一个方法专门用来获取对象的值,但是这个对象并不确定,我们就可以使用extends和keyof进行约束。
functiongetValue(obj:T,key:K){ returnobj[key] } constobj={a:1} consta=getValue(obj,'a')
这里的getValue方法就能根据传入的参数obj来约束key的值。
类型映射(in)
in关键词的作用主要是做类型的映射,遍历已有接口的key或者是遍历联合类型。下面使用内置的泛型接口Readonly来举例。
typeReadonly={ readonly[PinkeyofT]:T[P]; }; interfaceObj{ a:string b:string } typeReadOnlyObj=Readonly
我们可以结构下这个逻辑,首先keyofObj得到一个联合类型'a'|'b'。
interfaceObj{ a:string b:string } typeObjKeys='a'|'b' typeReadOnlyObj={ readonly[PinObjKeys]:Obj[P]; }
然后PinObjKeys相当于执行了一次forEach的逻辑,遍历'a'|'b'
typeReadOnlyObj={ readonlya:Obj['a']; readonlyb:Obj['b']; }
最后就可以得到一个新的接口。
interfaceReadOnlyObj{ readonlya:string; readonlyb:string; }
条件类型(U?X:Y)
条件类型的语法规则和三元表达式一致,经常用于一些类型不确定的情况。
TextendsU?X:Y
上面的意思就是,如果T是U的子集,就是类型X,否则为类型Y。下面使用内置的泛型接口Extract来举例。
typeExtract=TextendsU?T:never;
如果T中的类型在U存在,则返回,否则抛弃。假设我们两个类,有三个公共的属性,可以通过Extract提取这三个公共属性。
interfaceWorker{ name:string age:number email:string salary:number } interfaceStudent{ name:string age:number email:string grade:number } typeCommonKeys=Extract//'name'|'age'|'email'
工具泛型
TypesScript中内置了很多工具泛型,前面介绍过Readonly、Extract这两种,内置的泛型在TypeScript内置的lib.es5.d.ts中都有定义,所以不需要任何依赖都是可以直接使用的。下面看看一些经常使用的工具泛型吧。
typePartial={ [PinkeyofT]?:T[P] }
Partial用于将一个接口的所有属性设置为可选状态,首先通过keyofT,取出类型变量T的所有属性,然后通过in进行遍历,最后在属性后加上一个?。
我们通过TypeScript写React的组件的时候,如果组件的属性都有默认值的存在,我们就可以通过Partial将属性值都变成可选值。
importReactfrom'react' interfaceButtonProps{ type:'button'|'submit'|'reset' text:string disabled:boolean onClick:()=>void } //将按钮组件的props的属性都改为可选 constrender=(props:Partial={})=>{ constbaseProps={ disabled:false, type:'button', text:'HelloWorld', onClick:()=>{}, } constoptions={...baseProps,...props} return( ) }
Required
typeRequired={ [PinkeyofT]-?:T[P] }
Required的作用刚好与Partial相反,就是将接口中所有可选的属性改为必须的,区别就是把Partial里面的?替换成了-?。
Record
typeRecord={ [PinK]:T }
Record接受两个类型变量,Record生成的类型具有类型K中存在的属性,值为类型T。这里有一个比较疑惑的点就是给类型K加一个类型约束,extendskeyofany,我们可以先看看keyofany是个什么东西。
大致一直就是类型K被约束在string|number|symbol中,刚好就是对象的索引的类型,也就是类型K只能指定为这几种类型。
我们在业务代码中经常会构造某个对象的数组,但是数组不方便索引,所以我们有时候会把对象的某个字段拿出来作为索引,然后构造一个新的对象。假设有个商品列表的数组,要在商品列表中找到商品名为「每日坚果」的商品,我们一般通过遍历数组的方式来查找,比较繁琐,为了方便,我们就会把这个数组改写成对象。
interfaceGoods{ id:string name:string price:string image:string } constgoodsMap:Record={} constgoodsList:Goods[]=awaitfetch('server.com/goods/list') goodsList.forEach(goods=>{ goodsMap[goods.name]=goods })
Pick
typePick={ [PinK]:T[P] }
Pick主要用于提取接口的某几个属性。做过Todo工具的同学都知道,Todo工具只有编辑的时候才会填写描述信息,预览的时候只有标题和完成状态,所以我们可以通过Pick工具,提取Todo接口的两个属性,生成一个新的类型TodoPreview。
interfaceTodo{ title:string completed:boolean description:string } typeTodoPreview=Pickconsttodo:TodoPreview={ title:'Cleanroom', completed:false }
Exclude
typeExclude=TextendsU?never:T
Exclude的作用与之前介绍过的Extract刚好相反,如果T中的类型在U不存在,则返回,否则抛弃。现在我们拿之前的两个类举例,看看Exclude的返回结果。
interfaceWorker{ name:string age:number email:string salary:number } interfaceStudent{ name:string age:number email:string grade:number } typeExcludeKeys=Exclude//'name'|'age'|'email'
取出的是Worker在Student中不存在的salary。
Omit
typeOmit=Pick< T,Exclude >
Omit的作用刚好和Pick相反,先通过Exclude
interfaceTodo{ title:string completed:boolean description:string } typeTodoPreview=Omitconsttodo:TodoPreview={ title:'Cleanroom', completed:false }
总结
如果只是掌握了TypeScript的一些基础类型,可能很难游刃有余的去使用TypeScript,而且最近TypeScript发布了4.0的版本新增了更多功能,想要用好它只能不断的学习和掌握它。希望阅读本文的朋友都能有所收获,摆脱AnyScript。
到此这篇关于你不知道的TypeScript高级类型(小结)的文章就介绍到这了,更多相关TypeScript高级类型内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。