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

掌握TypeScript中的映射类型_0

时间:2023-03-17 10:55:04 科技观察

DRY原则(Don'trepeatyourself)是软件开发中最重要的原则之一,即不要重复自己。应避免代码中两个或多个地方的业务逻辑重复。在TypeScript中,映射类型可以帮助我们避免编写重复的代码,它可以根据现有类型和定义的规则创建新的类型。让我们来看看什么是映射类型以及如何构建自己的映射类型。1.基本概念在介绍映射类型之前,我们先来看一些前置知识。(1)IndexedAccessType在TypeScript中,我们可以通过名称查找来访问属性的类型:typeAppConfig={username:string;布局:字符串;};输入用户名=AppConfig["用户名"];本例中,通过AppConfigtype的索引用户名获取其类型字符串,类似于JavaScript中通过索引获取对象的属性值。(2)索引签名当类型属性的实际名称未知但它们将引用的数据类型已知时,索引签名很方便。输入User={名称:字符串;首选项:{[键:字符串]:字符串;}};constcurrentUser:User={name:'FooBar',preferences:{lang:'en',},};constcurrentLang=currentUser.preferences.lang;在上面的例子中,currentLang的类型是string而不是any。此功能与keyof运算符配合使用,是使映射类型成为可能的核心要素之一。(3)接头类型接头类型是两种或多种类型的组合。它表示值的类型可以是联合中包含的任何一种类型。类型StringOrNumberUnion=字符串|数字;让值:StringOrNumberUnion='你好,世界!';值=100;这是一个更复杂的示例,其中编译器可以为联合类型提供一些高级保护:typeAnimal={name:string;物种:字符串;};类型Person={名称:字符串;年龄:数字;};类型AnimalOrPerson=Animal|人;常量值:AnimalOrPerson=loadFromSomewhereElse();console.log(value.name);//?控制台。日志(价值。年龄);//?if('age'invalue){console.log(value.age);//?}在这个例子中,因为Animal和Person都有name属性,所以第15行的value.name可以正常输出,没有报错。而第16行的value.age会被编译错误,因为如果value是Animal类型,那么该value就没有age属性。在第19行的if块中,只能输入此代码块,因为该值具有age属性。所以在这个if块中,value一定是Person,TS可以知道这个value一定有age属性,所以编译是正确的。(4)keyof类型运算符keyof类型运算符返回传递给它的类型的键的联合。输入AppConfig={用户名:字符串;布局:字符串;};输入AppConfigKey=keyofAppConfig;在此示例中,AppConfigKey类型将被解析为“用户名”|“布局”。它可以与索引签名一起使用:typeUser={name:string;首选项:{[键:字符串]:字符串;}};输入UserPreferenceKey=keyofUser["preferences"];在这里,UserPreferenceKeytype被解析为字符串|数字。(5)元组类型元组是一种特殊类型的数组,其中数组的元素在特定索引处可能是特定类型。它们允许TypeScript编译器围绕值数组提供更多安全性,尤其是当这些值属于不同类型时。例如,TypeScript编译器能够为元组的各种元素提供类型安全:typeCurrency=[number,string];常量:货币=[100,''];functionadd(values:number[]){returnvalues.reduce((a,b)=>a+b);}add(amount);//错误:“货币”类型的参数不可分配给类型的参数'number[]'.//类型'string'不可分配给类型'number'。上面的代码会报错。不能将Currency类型的参数赋值给“number[]”类型的参数,也不能将字符串类型赋值给number类型。当访问超出元组定义类型的索引处的元素时,TypeScript可以提示:typeLatLong=[number,number];constloc:LatLong=[48.858370,2.294481];console.log(loc[2]);//错误:长度为“2”的元组类型“LatLong”在索引“2”处没有元素。在这里,元组类型LatLong只有两个元素。当试图访问第三个元素时,会报错。(6)条件类型条件类型是一个表达式,类似于JavaScript中的三元表达式。它的语法如下:TextendsU?X:Y让我们看一个实际的例子:typeConditionalType=stringextendsboolean?字符串:布尔值;在上面的示例中,ConditionalType的类型将为boolean,因为条件字符串extendsboolean始终为false。2.映射类型(1)初体验在TypeScript中,当您需要从另一种类型派生(并保持同步)另一种类型时,使用映射类型特别有用。//用户的配置值typeAppConfig={username:string;布局:字符串;};//用户是否有权限更改配置值typeAppPermissions={changeUsername:boolean;改变布局:布尔值;};上述代码中,AppConfig与AppPermissions之间存在隐式关系,每当AppConfig添加新的配置值时,A??ppPermissions中也必须有对应的布尔值。这里可以使用映射类型来管理两者的关系:typeAppConfig={username:string;布局:字符串;};typeAppPermissions={[AppConfigkeyof中的Propertyas`change${Capitalize}`]:boolean};上面的代码中,只要AppConfig中的type发生变化,AppPermissions就会随之变化。实现了两者的映射关系。(2)概念在TypeScript和JavaScript中,最常见的映射是Array.prototype.map():[1,2,3].map(value=>value.toString());//["1","2","3"]在这里,我们将数组中的数字映射到它们的字符串表示形式。因此,TypeScript中的映射类型意味着通过转换其每个属性将一种类型转换为另一种类型。(3)例子让我们用一个例子来深入理解映射类型。为设备定义以下类型,其中包含制造商和价格属性:typeDevice={manufacturer:string;价格:数字;};为了让用户更容易理解设备信息,在对象中增加了一个新的类型,可以使用适当的格式化设备的每个属性:typeDeviceFormatter={[KeyinkeyofDeviceas`format${Capitalize}`]:(value:Device[Key])=>string;};解决上面的代码。KeyinkeyofDevice使用keyof类型运算符生成Device中所有键的并集。将它放在索引签名中实际上是遍历Device的所有属性并将它们映射到DeviceFormatter的属性。format${Capitalize}是映射的转换部分,它使用键重映射和模板文字类型将属性名称从x更改为formatX。(value:Device[Key])=>string;使用索引访问类型Device[Key]表示格式化函数的value参数为格式化属性的类型。因此,formatManufacturer接受一个字符串(制造商),而formatPrice接受一个数字(价格)。DeviceFormatter类型如下所示:typeDeviceFormatter={formatManufacturer:(value:string)=>string;formatPrice:(value:number)=>string;};现在,假设将第三个属性releaseYear添加到Device类型:typeDevice={manufacturer:string;价格:数量;releaseYear:number;}由于映射类型的强大功能,DeviceFormatter类型自动扩展为以下类型,无需任何额外工作:typeDeviceFormatter={formatManufacturer:(value:string)=>string;格式价格:(值:数字)=>字符串;formatReleaseYear:(value:number)=>string;};3.实用程序中的映射TypeScript自带许多用作实用程序的映射类型,最常见的包括Omit、Partial、Readonly、Readonly、Exclude、Extract、NonNullable、ReturnType等,让我们看看其中两种是如何构建的。(1)PartialPartial是一种映射类型,它将现有的类型属性转换为可选类型,并通过与undefined的联合使该类型可为空。接口Point3D{x:数字;y:数字;z:number;}typePartialPoint3D=Partial;这里的PartialPoint3D类型其实是这样的:typePartialPoint3D={x?:number;y?:数字;z?:number;}当我们将鼠标悬停在Partial上时,我们会看到它的定义:将其取出:typePartial={[PinkeyofT]?:T[P]|不明确的;Disassemble这行代码来了:使用泛型传递目标接口T。使用keyofT获取T的所有键。使用[PinkeyofT]访问并循环遍历所有键。它通过添加?使密钥可选。使用联合类型T[P]|undefined使键的类型可为空。(2)ExcludeExclude是一种映射类型,它允许有选择地从类型中删除属性。它定义如下:typeExclude=TextendsU?never:T它从T中排除那些可以通过使用条件类型分配给U的类型,并在排除的属性上返回nerver。输入动物='鸟'|'猫'|'crocodile';typemammals=Exclude;//'鸟'|'cat'4.通过上述TypeScript内置实用类型构建映射类型原理的讲解让大家对映射类型有了更深刻的理解。最后,我们来构建自己的映射类型:Optional,它可以使原始类型中指定键的类型可选且为空。我们可以这样做:将整个类型转换为Optional。从这个新类型中仅选择所需的属性,使它们成为可选的。将原始类型与排除的属性连接起来。实现代码和测试用例如下:typeOptional=Pick,K>&Omit;类型Person={名称:字符串;姓氏:字符串;email:string;}typeUser=Optional;//现在电子邮件属性是可选的typeAnonymousUser=Optional;//现在email和surname属性是可选的注意这里使用KextendskeyofT来确保只传递属于类型/接口的属性。否则,TypeScript会在编译时抛出错误。映射类型的一大优点是它们的可组合性:它们可以组合起来创建新的映射类型。上面使用现有的实用程序类型实现了我们想要的Optional。当然,我们也可以在不使用任何其他映射类型的情况下重新创建Optional映射类型实用程序:typeOptional={[PinK]?:T[P]}&{[PinExclude]:T[P]};上面的代码结合了两种类型:第一种类型通过使用?修饰符。第二种通过Excluse获取剩余的key。