当前位置: 首页 > 科技观察

你不知道的TypeScript高级类型

时间:2023-03-13 18:12:49 科技观察

前言对于有JavaScript基础的同学来说,TypeScript入门其实非常容易。你只需要简单掌握它的基本类型系统,就可以逐步从JS应用过渡到TS应用。//jsconstdouble=(num)=>2*num//tsconstdouble=(num:number):number=>2*num但是当应用越来越复杂的时候,我们可以很方便的将一些变量设置为任意类型,TypeScriptwrite写的时候就变成了AnyScript。为了让大家对TypeScript的类型系统有更深入的了解,本文将重点介绍其高级类型,帮助大家摆脱AnyScript。泛型在讲解高级类型之前,我们需要简单了解一下什么是泛型。泛型是强类型语言中的一个重要概念。合理使用泛型可以提高代码的复用性,使系统更加灵活。以下是维基百科对泛型的描述:泛型允许程序员在用强类型编程语言编写代码时使用一些后面指定的类型,并在实例化时将这些类型指定为参数。泛型用一对尖括号(<>)表示,尖括号内的字符称为类型变量,用来表示类型。functioncopy(arg:T):T{if(typeofarg==='object'){returnJSON.parse(JSON.stringify(arg))}else{returnarg}}这个类型T,当copy函数没有called的时候是不确定的,只有调用copy的时候,才知道T代表什么类型。conststr=copy('mynameistypescript')type我们在VSCode中可以看到,copy函数的参数和返回值已经有了类型,也就是说我们调用copy函数的时候,类型变量T被赋值了字符串的值。其实我们在调用copy的时候可以省略尖括号,通过TS的类型推导就可以确定T是一个字符串。类型推导高级类型除了string、number、boolean等基本类型外,我们还应该了解一些类型声明中的一些高级用法。交集类型(&)交集类型简单来说就是将多种类型组合成一种类型。个人认为称其为“合并类型”更为合理,其语法规则与逻辑“AND”符号一致。T&U如果我现在有两个类,一个按钮和一个超链接,现在我需要一个带有超链接的按钮,我可以使用交叉类型来实现它。interfaceButton{type:stringtext:string}interfaceLink{alt:stringhref: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:'button'}类型别名(type)前面提到的intersectiontype和jointtype如果有有多个地方需要用到,需要通过类型别名为这两个类型声明别名。类型别名类似于声明变量的语法,只需将const和let替换为type关键字即可。typeAlias=T|UtypeInnerType='default'|'primary'|'danger'interfaceButton{type:InnerTypetext:string}interfaceAlert{type:ButtonTypetext:string}类型索引(keyof)keyof类似于Object.keys,用来获取一个interface中Key的联合类型。interfaceButton{type:stringtext:string}typeButtonKeys=keyofButton//等同于typeButtonKeys="type"|"text"或者以前面的Button类为例,Button的type类型来自另一个类ButtonTypes,按照前面的写法方法,每次ButtonTypes更新都需要修改Button类。如果我们使用keyof,我们就不会遇到这个麻烦。interfaceButtonStyle{color:stringbackground:string}interfaceButtonTypes{default:ButtonStyleprimary:ButtonStyledanger:ButtonStyle}interfaceButton{type:'default'|'primary'|'danger'text:string}//使用keyof后,修改ButtonTypes后,类型type会自动修改interfaceButton{type:keyofButtonTypestext:string}类型约束(extends)这里的extends关键字不同于在class之后使用extends的继承功能。usingwithingeneric的主要作用是约束泛型。再用前面写的copy方法再举个例子:typeBaseType=string|number|boolean//这里表示copy的参数//只有string、number和Boolean函数的基本类型copy(arg:T):T{returnarg}copynumber如果我们传入一个对象就会有问题。copyobjectextends经常和keyof一起使用,比如我们有一个方法专门用来获取对象的值,但是这个对象不确定,我们可以用extends和keyof来约束。functiongetValue(obj:T,key:K){returnobj[key]}constobj={a:1}consta=getValue(obj,'a')获取这里对象的值getValue方法可以根据传入参数obj来约束key的取值。类型映射(in)in关键字的作用主要是做类型映射,遍历已有接口的键或者遍历联合类型。下面以内置泛型接口Readonly为例。typeReadonly={readonly[PinkeyofT]:T[P];};interfaceObj{a:stringb:string}typeReadOnlyObj=ReadonlyReadOnlyObj我们可以构建这个逻辑,首先keyofObj得到一个联合类型'a'|'b'。interfaceObj{a:stringb:string}typeObjKeys='a'|'b'typeReadOnlyObj={readonly[PinObjKeys]:Obj[P];}那么ObjKeys中的P相当于执行一次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:stringage:numberemail:stringsalary:number}interfaceStudent{name:stringage:numberemail:stringgrade:number}typeCommonKeys=Extract//'name'|'age'|'email'CommonKeys工具GenericTypesScript有内置了很多工具泛型,Readonly和Extract前面介绍过。内置泛型定义在TypeScript内置的lib.es5.d.ts中,因此可以直接使用,无需任何依赖。让我们来看看一些常用的工具泛型。lib.es5.d.tsPartialtypePartial={[PinkeyofT]?:T[P]}Partial用于将接口的所有属性设置为可选状态,首先通过keyof取出类型变量T的所有属性T,然后遍历in,最后加一个?在属性之后。当我们通过TypeScript编写React组件时,如果组件的属性有默认值,我们可以使用Partial将属性值更改为可选值。importReactfrom'react'interfaceButtonProps{type:'button'|'submit'|'reset'text:stringdisabled:booleanonClick:()=>void}//将按钮组件的props属性改为optionalconstrender=(props:Partial={})=>{constbaseProps={disabled:false,type:'button',text:'HelloWorld',onClick:()=>{},}constoptions={...baseProps,...props}return({options.text})}RequiredtypeRequired={[PinkeyofT]-?:T[P]}Required的作用正好与Partial相反,将接口中的所有可选属性都改为required。不同之处在于?inPartial被-?取代。RecordtypeRecord={[PinK]:T}Record接受两个类型变量,Record生成一个类型,其属性存在于类型K中,值为类型T。这里一个令人费解的地方是为类型添加类型约束K,extendskeyofany,我们可以先看看keyofany是什么。keyofany大致总是类型K受限于string|编号|symbol,恰好是对象索引的类型,即类型K只能指定为这些类型。我们在业务代码中经常会构造一个对象数组,但是数组不方便索引,所以有时候会取出一个对象的某个字段作为索引,然后构造一个新的对象。假设有一个产品列表数组。在商品列表中查找名为“每日坚果”的商品,我们通常是通过遍历数组来查找,比较麻烦。为了方便起见,我们将这个数组重写为一个对象。interfaceGoods{id:stringname:stringprice:stringimage:string}constgoodsMap:Record={}constgoodsList:Goods[]=awaitfetch('server.com/goods/list')goodsList.forEach(goods=>{goodsMap[goods.name]=goods})PicktypePick={[PinK]:T[P]}Pick主要用来提取界面的一些属性。做过Todo工具的同学都知道,Todo工具在编辑的时候只会填写描述信息,预览的时候只会显示标题和完成状态。因此,我们可以使用Pick工具将Todo接口的两个属性提取出来,生成一个新的类型。待办事项预览。interfaceTodo{title:stringcompleted:booleanddescription:string}typeTodoPreview=Pickconsttodo:TodoPreview={title:'Cleanroom',completed:false}TodoPreviewExcludetypeExclude=TextendsU?从不:TExclude的作用与之前介绍的Extract正好相反。如果T中的类型在U中不存在,则返回,否则丢弃。下面我们以前面两个类为例,看看Exclude的返回结果。interfaceWorker{name:stringage:numberemail:stringsalary:number}interfaceStudent{name:stringage:numberemail:stringgrade:number}typeExcludeKeys=Exclude//'name'|'age'|'email'ExcludeKeys为工人A薪水在Student中不存在。OmittypeOmit=Pick>Omit的作用正好与Pick相反。首先通过Exclude抽取类型T中存在而K中不存在的属性,然后根据这些属性构造一个新的属性。新类型。还是从之前的Todo案例来看,TodoPreview类型只需要排除接口的description属性,写法比之前的Pick更简单。interfaceTodo{title:stringcompleted:booleanddescription:string}typeTodoPreview=Omitconsttodo:TodoPreview={title:'Cleanroom',completed:false}TodoPreview总结如果你只是掌握了TypeScript的一些基本类型,可能你可以轻松使用TypeScript,最近TypeScript4.0增加了更多功能。要想用好它,只能不断地学习和掌握。希望看到这篇文章的朋友们能够有所收获,摆脱AnyScript。本文转载自微信公众号《更神奇的前端》,可通过以下二维码关注。转载本文请联系更牛逼的前端公众号。