写了这么多年TypeScript,最大的感受就是非常通俗易懂——尤其是对于有Java背景的人来说。然而,在听到TypeScript4.1(该语言的最新重大更新)的消息后,我仍然对这些功能的新鲜感感到惊讶。我不认为我是一个无知的例外。在利用这个新闻作为深入了解类型系统实际工作原理的机会之后,我想与您分享新版本中令人兴奋的功能和变化,以及关键字解释和许多令人惊叹的示例。如果您在TypeScript语言方面有扎实的基础并且渴望学习高级功能,那么就来这里吧。新语言特性模板字面量类型从ES6开始,我们可以使用模板字面量(TemplateLiterals)特性来编写带有反引号的字符串,而不仅仅是单引号或双引号:constmessage=`text`;就像Flavio根据Copes所说,模板文字提供了以前用引号写的字符串所没有的特性:定义多行字符串非常方便可以轻松地插入变量和表达式可以使用模板标签创建DSLlanguage)templateliteraltype与JavaScript中的模板字符串语法完全相同,但在类型定义中使用:typeEntity='Invoice';typeNotification=`${Entity}saved`;//等于//typeNotification='Invoicesaved';typeViewport='md'|'xs';typeDevice='mobile'|'desktop';typeScreen=`${Viewport|Device}screen`;//相当于下面一行//typeScreen='mdscreen'|'xsscreen'|'mobilescreen'|'desktopscreen';当我们定义一个特定的字面量类型时,TypeScript会通过拼接内容的方式生成一个新的字符串字面量类型。键值对类型中的键重映射(KeyRemapping)映射类型可以根据任意键创建新的对象类型。字符串文字可以用作映射类型中的属性名称:typeActions={[Kin'showEdit'|'showCopy'|'showDelete']?:boolean;};//等价于typeActions={showEdit?:boolean,showCopy?:布尔值,showDelete?:布尔值};如果您想创建新键或过滤掉键,TypeScript4.1允许您使用新的as子句重新映射映射类型中的键:typeMappedTypeWithNewKeys={[KinkeyofTasNewKeyType]:T[K]}TypeScriptRemappingKeys您可以利用模板文字类型等功能轻松地基于旧属性名称创建新属性名称。可以通过生成never来过滤键,这样在某些情况下您就不必使用额外的Omit辅助类型:通过使用新的as子句,我们可以利用模板文字类型等功能轻松地创建基于的新属性名称旧的。我们可以通过输出never来过滤键,这样在某些情况下我们就不必使用额外的Omit辅助类型:typeGetters={[KinkeyofTas`get${Capitalize}`]:()=>T[K]};interfacePerson{name:string;age:number;location:string;}typeLazyPerson=Getters;//^=typeLazyPerson={//getName:()=>string;//getAge:()=>number;//getLocation:()=>string;///移除'kind'属性typeRemoveKindField={[KinkeyofTasExclude]:T[K]};interfaceCircle{kind:"circle";radius:number;}typeKindlessCircle=RemoveKindField;//^=typeKindlessCircle={//radius:number;//}TypeScript利用带有as子句的模板文字类型(来源)JSX工厂函数JSX代表JavaScriptXML,它允许我们使用JavaScript编写HTML元素并将它们放置在DOM中,而无需任何createElement()或appendChild()方法,例如:constgreeting=YesIcandoit!
;ReactDOM.render(greeting,document.getElementById('root'));TypeScript4.1通过编译器选项jsx的两个新选项支持React17的jsx和jsxs工厂函数:react-jsxreact-jsxdev这些选项分别用于生产和开发编译。一般来说,一个选项可以从另一个选项扩展。"—TypeScript发行说明以下是用于生产和开发的TypeScript配置文件的两个示例://./src/tsconfig.json{"compilerOptions":{"module":"esnext","target":"es2015","jsx":"react-jsx","strict":true},"include":["./**/*"]}开发配置://./src/tsconfig.dev.json{"extends":"./tsconfig.json","compilerOptions":{"jsx":"react-jsxdev"}}如下所示,TypeScript4.1支持在类似React的JSX环境中进行类型检查:Recursiveconditionaltypes另一个新增的是recursiveconditionaltypes,这允许它们在分支中引用自己,允许更灵活地处理条件类型并更容易编写递归类型别名。这是一个使用Awaited展开深度嵌套示例的Promise:typeAwaited=TextendsPromiseLike?Awaited:T;//类似于`promise.then(...)`,但在类型上更精确declarefunctioncustomThen(p:Promise,onFulfilled:(value:Awaited)=>U):Promise>;但它应该请注意,TypeScript需要更多时间来类型检查递归类型。Microsoft警告您应该负责任并小心地使用它们。CheckedindexedaccessesIndexedaccessesCheck_Index签名在TypeScript中允许访问任意命名的属性,如下面的Options接口:propName:string]:string|number;}functioncheckOptions(opts:Options){opts.path;//stringopts.permissions;//number//这些都可以!因为类型都是string|numberopts.yadda.toString();opts["foobarbaz"].toString();opts[Math.random()].toString();}这里我们看到不是path和permissionsPropertiesshouldhavetypestring|number:TypeScript4.1提供了一个新标志--noUncheckedIndexedAccess使每个属性访问(如opts.path)或索引访问(如opts["blabla"])都可能未定义。这意味着如果我们需要访问像前面示例中的opts.path这样的属性,我们必须检查它是否存在或使用非空断言运算符(后缀!字符):functioncheckOptions(opts:Options){opts.path;//stringopts.permissions;//number//启用noUncheckedIndexedAccess时下面的代码是非法的opts.yadda.toString();opts["foobarbaz"].toString();opts[Math.random()].toString();//检查属性是否真的存在if(opts.yadda){console.log(opts.yadda.toString());}//直接使用非空断言操作符opts.yadda!.toString();--noUncheckedIndexedAccess标志对于捕获大量错误很有用,但对于大量代码来说可能很嘈杂。这就是--strict开关不会自动启用它的原因。baseUrl无需指定路径在TypeScript4.1之前,为了能够在tsconfig.json文件中使用路径,必须声明baseUrl参数。在新版本中,可以在没有路径选项的情况下指定baseUrl。这解决了自动导入路径不佳的问题。{"compilerOptions":{"baseUrl":"./src","paths":{"@shared":["@shared/"]//Thismappingisrelativeto"baseUrl"}}}checkJs默认开启allowJs如果你有一个JavaScript项目,其中使用checkJs选项检查.js文件是否存在错误,还应声明allowJs以允许编译JavaScript文件。在TypeScript4.1中,checkJs默认表示allowJs:{compilerOptions:{allowJs:true,checkJs:true}}JSDoc@see标签编辑器支持在编辑器中使用TypeScript时,现在有对JSDoc标签@see更好的支持,这将提高TypeScript4.1的可用性://@filename:first.tsexportclassC{}//@filename:main.tsimport*asfirstfrom"./first";/***@seefirst.C*/functionrelated(){}incompatibleAmbient改变lib.d.ts的结构和DOM的声明,使您可以轻松地开始编写类型检查的JavaScript代码。该文件自动包含在TypeScript项目的编译上下文中。您可以通过指定--noLib编译器命令行标志或在tsconfig.json中将noLib配置为true来排除它。在TypeScript4.1中,lib.d.ts可能有一组更改的API,因为DOM类型是自动生成的,例如Reflect.enumerate已从ES2016中移除。抽象成员不能再被标记为异步在另一个重大变化中,标记为抽象的成员不能再被标记为异步。因此,要修复代码,必须删除async关键字:abstractclassMyClass{//asyncmustberemovedinTypeScript4.1abstractasynccreate():Promise;对于像somethingElse这样的表达式,foo是any或unknown类型。整个表达式的类型将是somethingElse的类型,在以下示例中为{someProp:string}:declareletfoo:unknown;declareletsomethingElse:{someProp:string};letx=foo&&somethingElse;在TypeScript4.1中,any和unknown都会向外展开,而不是在右侧。通常,此更改的正确解决方法是从foo&&someExpression切换到!!foo&&someExpression。注意:双感叹号(!!)是将变量强制转换为布尔值(true或false)的便捷方式。Promise中resolve的参数不再是可选的。Promise中resolve的参数不再是可选的,比如下面的代码:newPromise((resolve)=>{doSomethingAsync(()=>{doSomething();resolve());});});这段代码在TypeScript4.1中编译会报错:resolve()~~~~~~~~~errorTS2554:Expected1arguments,butgot0.Aargumentfor'value'wasnotprovided。要解决这个问题,必须使用Promise提供至少一个值来resolvein,否则,在确实需要不带参数调用resolve()的情况下,必须使用显式void泛型类型参数声明Promise:newPromise((resolve)=>{doSomethingAsync(()=>{doSomething();resolve();});});条件扩展将创建可选属性在JavaScript中,扩展运算符{...files}不适用于false值,例如files为null或undefined。在以下使用条件传播的示例中,如果定义了文件,则将传播file.owner的属性。否则,不会将任何属性传播到返回的对象中:object:{x:number}|{x:number,name:string,age:number,location:string}如果定义了文件,它将具有Person(所有者的类型)的所有属性。否则,将不会显示任何结果,但事实证明,这最终可能会非常昂贵,而且通常无济于事。单个对象中有数百个扩展对象,每个扩展对象可能会添加数百或数千个属性。为了更好的性能,在TypeScript4.1中,返回的类型有时会使用所有可选属性:{x:number;name?:string;age?:number;location?:string;}不匹配的参数将不再关联过去,参数不对应的在TypeScript中通过将它们与any类型相关联而相互关联。在下面的重载示例中(为同一个函数提供多种函数类型),pickCard函数将根据用户传入的内容返回两种不同的东西。如果用户传入一个代表牌组的对象,该函数将选择卡片.如果用户选择一张牌,他们将得到他们选择的牌:suits=["hearts","spades","clubs","diamonds"];functionpickCard(x:{suit:string;card:number}[]):number;functionpickCard(x:number):{suit:string;card:number};functionpickCard(x:any):any{//检查是否我们正在使用一个对象/数组//如果是这样,他们给了我们的卡片,我们将选择卡片(typeofx=="object"){letpickedCard=Math.floor(Math.random()*x.length);returnpickedCard;}//否则就让他们pickthecardelsif(typeofx=="number"){letpickedSuit=Math.floor(x/13);return{suit:suits[pickedSuit],card:x%13};}}letmyDeck=[{suit:"diamonds",card:2},{suit:"spades",card:10},{suit:"hearts",card:4},];letpickedCard1=myDeck[pickCard(myDeck)];alert("card:"+pickedCard1.card+"of"+pickedCard1.suit);letpickedCard2=pickCard(15);alert("card:"+pickedCard2.card+"of"+pickedCard2.suit);使用TypeScript4.1,赋值在某些情况下会失败,重载解析在某些情况下会失败。作为解决方法,最好使用类型断言来避免错误。最后一个想法TypeScript通过在运行代码之前捕获错误并提供修复来节省我们的时间。通过对TypeScript的深入理解,我们可以更好地理解如何改进代码结构并获得解决复杂问题的方法。希望本文能帮助您探索类型系统,让您的编程之旅更加精彩。TypeScript4.1可通过NuGet或NPM获得:npminstalltypescript