今天,我们很高兴地宣布TypeScript5。0的测试版发布! 此版本带来了许多新功能,同时旨在使TypeScript更小、更简单、更快。我们已经实现了新的装饰器标准、更好地支持Node和捆绑器中的ESM项目的功能、库作者控制泛型推理的新方法、扩展了我们的JSDoc功能、简化了配置,并进行了许多其他改进。 虽然5。0版本包括正确性更改和对较少使用的标志的弃用,但我们相信大多数用户将拥有与以前版本类似的升级体验。 要开始使用测试版,您可以通过NuGet获取它,或通过以下命令使用npm:npminstalltypescriptbeta装饰器 装饰器是即将推出的ECMAScript功能,它允许我们以可重用的方式自定义类及其成员。 让我们考虑以下代码:classPerson{name:string;constructor(name:string){this。namename;}greet(){console。log(Hello,mynameis{this。name}。);}}constpnewPerson(Ray);p。greet(); greet在这里非常简单,但让我们想象它是更复杂的东西也许它执行一些异步逻辑,它是递归的,它有副作用等等。不管你想象的是哪种,假设你调用console。log输出一些以帮助调试问候语。classPerson{name:string;constructor(name:string){this。namename;}greet(){console。log(LOG:Enteringmethod。);console。log(Hello,mynameis{this。name}。);console。log(LOG:Exitingmethod。)}} 这种模式相当常见。如果有一种方法可以让我们对每个方法都这样做,那就太好了! 这就是装饰器的作用。我们可以编写一个名为loggedMethod的函数,如下所示:functionloggedMethod(originalMethod:any,context:any){functionreplacementMethod(this:any,。。。args:any〔〕){console。log(LOG:Enteringmethod。)constresultoriginalMethod。call(this,。。。args);console。log(LOG:Exitingmethod。)returnresult;}returnreplacementMethod;} 这些any是怎么回事? 耐心点我们现在让事情变得简单,这样我们就可以专注于这个函数在做什么。注意,loggedMethod接受原始方法(originalMethod)并返回一个函数记录一条输入(Entering)的消息将this及其所有参数传递给原始方法记录退出(Exiting)消息返回原始方法返回的值 现在我们可以使用loggedMethod来装饰greet:classPerson{name:string;constructor(name:string){this。namename;}loggedMethodgreet(){console。log(Hello,mynameis{this。name}。);}}constpnewPerson(Ray);p。greet();Output:LOG:Enteringmethod。Hello,mynameisRay。LOG:Exitingmethod。 我们只是在greet上面使用了loggedMethod作为装饰器注意,我们把它写成了loggedMethod。当我们这样做时,它会被target方法和一个context对象调用。因为loggedMethod返回了一个新函数,所以这个函数替换了greet的原始定义。 loggedMethod定义了第二个参数。它被称为上下文对象,它有一些关于修饰方法是如何声明的有用信息比如它是private成员或者是静态的,或者方法的名称是什么。让我们重写loggedMethod来利用这一点,并打印出被装饰的方法的名称。functionloggedMethod(originalMethod:any,context:ClassMethodDecoratorContext){constmethodNameString(context。name);functionreplacementMethod(this:any,。。。args:any〔〕){console。log(LOG:Enteringmethod{methodName}。)constresultoriginalMethod。call(this,。。。args);console。log(LOG:Exitingmethod{methodName}。)returnresult;}returnreplacementMethod;} 我们现在使用context参数它是loggedMethod中第一个类型比any和any〔〕更严格的参数。TypeScript提供了一个名为ClassMethodDecoratorContext的类型,它为方法装饰器接受的上下文对象建模。 除了元数据,方法的上下文对象还有一个有用的函数addInitializer。这是一种挂接到构造函数开头的方式(如果使用静态方法,则挂接到类本身的初始化)。 举个例子在JavaScript中,经常会写如下的模式:classPerson{name:string;constructor(name:string){this。namename;this。greetthis。greet。bind(this);}greet(){console。log(Hello,mynameis{this。name}。);}} 或者,可以将greet声明为一个初始化为箭头函数。classPerson{name:string;constructor(name:string){this。namename;}greet(){console。log(Hello,mynameis{this。name}。);};} 编写这段代码是为了确保当greet作为独立函数调用或作为回调函数传递时,不会重新绑定。constgreetnewPerson(Ray)。greet;我们不想这里出错greet(); 我们可以编写一个装饰器,使用addInitializer在构造函数中为我们调用bind。functionbound(originalMethod:any,context:ClassMethodDecoratorContext){constmethodNamecontext。name;if(context。private){thrownewError(boundcannotdecorateprivatepropertieslike{methodNameasstring}。);}context。addInitializer(function(){this〔methodName〕this〔methodName〕。bind(this);});} 当Bound装饰一个方法时它不会返回任何东西,它会保留原来的方法。相反,它会在其他字段初始化之前添加逻辑。classPerson{name:string;constructor(name:string){this。namename;}boundloggedMethodgreet(){console。log(Hello,mynameis{this。name}。);}}constpnewPerson(Ray);constgreetp。greet;Works!greet(); 注意,我们用了两个装饰器bound和loggedMethod。这些装饰是以相反的顺序运行的。也就是说,loggedMethod修饰了原始方法greet,bound修饰了loggedMethod的结果。在这个例子中,这没有关系但如果你的装饰器有副作用或期望某种顺序,则需要关注他们的顺序。 同样值得注意的是如果你更喜欢代码风格化,你可以将这些装饰器放在同一行。boundloggedMethodgreet(){console。log(Hello,mynameis{this。name}。);} 可能不太明显的是,我们甚至可以创建返回装饰器函数的函数。这使得我们可以对最终的装饰器进行一些自定义。如果我们愿意,我们可以让loggedMethod返回一个装饰器,并自定义它记录消息的方式。functionloggedMethod(headMessageLOG:){returnfunctionactualDecorator(originalMethod:any,context:ClassMethodDecoratorContext){constmethodNameString(context。name);functionreplacementMethod(this:any,。。。args:any〔〕){console。log({headMessage}Enteringmethod{methodName}。)constresultoriginalMethod。call(this,。。。args);console。log({headMessage}Exitingmethod{methodName}。)returnresult;}returnreplacementMethod;}} 如果我们这样做,我们必须在使用loggedMethod作为装饰器之前调用它。然后,我们可以传入任何字符串作为输出到控制台的消息的前缀。classPerson{name:string;constructor(name:string){this。namename;}loggedMethod()greet(){console。log(Hello,mynameis{this。name}。);}}constpnewPerson(Ray);p。greet();Output:Enteringmethodgreet。Hello,mynameisRay。Exitingmethodgreet。 装饰器可不仅仅用于方法!它们可以用于属性字段、getter、setter和自动访问器。甚至类本身也可以装饰成子类化和注册。 有关所涉及更改的更多信息,您可以查看原始的pullrequest。与实验性遗留装饰器的差异 如果你已经使用TypeScript一段时间了,你可能会意识到它多年来一直支持实验性装饰器。虽然这些实验性装饰器非常有用,但它们模拟了装饰器提案的一个更旧的版本,并且总是需要一个称为experimentalDecorators的可选编译器标志。任何在TypeScript中不使用此标志的尝试都会提示错误。 实验性装饰器将在可预见的未来继续存在;但是,如果没有这个标志,装饰器将成为所有新代码的有效语法。在experimentalDecorators之外,它们将被类型检查并以不同的方式发出。类型检查规则和emit有很大的不同,虽然可以编写装饰器来同时支持新旧装饰器行为,但任何现有的装饰器函数都不太可能这样做。 这个新的装饰器提案与emitDecoratorMetadata不兼容,而且它不允许装饰参数。未来的ECMAScript提案可能有助于弥合这一差距。 最后说一句:目前,装饰器的提案要求类装饰器在export关键字之后(如果存在的话)。exportregisterclassFoo{。。。}exportComponent({。。。})classBar{。。。} TypeScript会在JavaScript文件中强制执行此限制,但TypeScript文件不会这样做。这部分是由现有用户驱动的我们希望在我们原始的实验性装饰器和标准化装饰器之间提供一个稍微简单的迁移路径。此外,我们从许多用户那里听到了对原始样式的偏好,我们希望在未来的标准讨论中真诚地讨论这个问题。编写类型良好的装饰器 上面的loggedMethod和bound装饰器示例很简单,并省略了大量关于类型的细节。 输入装饰器可能相当复杂。例如,上面的loggedMethod类型良好的版本可能看起来像这样:functionloggedMethodThis,Argsextendsany〔〕,Return(target:(this:This,。。。args:Args)Return,context:ClassMethodDecoratorContextThis,(this:This,。。。args:Args)Return){constmethodNameString(context。name);functionreplacementMethod(this:This,。。。args:Args):Return{console。log(LOG:Enteringmethod{methodName}。)constresulttarget。call(this,。。。args);console。log(LOG:Exitingmethod{methodName}。)returnresult;}returnreplacementMethod;} 我们必须使用this、Args和return类型参数分别建模this、参数和原始方法的返回类型。 具体定义装饰器函数的复杂程度取决于你想要什么。请记住,你的装饰器的使用次数将超过它们的编写次数,所以类型良好的版本通常是更好的但显然需要与可读性权衡,所以请尽量保持简单。 constType参数 在推断对象的类型时,TypeScript通常会选择一种通用的类型。例如,在这个例子中,names的推断类型是string〔〕:typeHasNames{readonlynames:string〔〕};functiongetNamesExactlyTextendsHasNames(arg:T):T〔names〕{returnarg。names;}Inferredtype:string〔〕constnamesgetNamesExactly({names:〔Alice,Bob,Eve〕}); 通常这样做的目的是实现突变。 然而,根据getnames确切的作用以及它的使用方式,通常情况下需要更具体的类型。 到目前为止,API作者通常不得不建议在某些地方添加const,以实现所需的推断:希望类型是:readonly〔Alice,Bob,Eve〕但类型变成:string〔〕constnames1getNamesExactly({names:〔Alice,Bob,Eve〕});正确获得想要的类型:readonly〔Alice,Bob,Eve〕constnames2getNamesExactly({names:〔Alice,Bob,Eve〕}asconst); 这可能很麻烦,也很容易忘记。在TypeScript5。0中,你现在可以在类型参数声明中添加const修饰符,以使const类推断成为默认值:typeHasNames{names:readonlystring〔〕};functiongetNamesExactlyconstTextendsHasNames(arg:T):T〔names〕{returnarg。names;}Inferredtype:readonly〔Alice,Bob,Eve〕Note:DidntneedtowriteasconsthereconstnamesgetNamesExactly({names:〔Alice,Bob,Eve〕}); 注意,const修饰符并不排斥可变值,也不需要不可变约束。使用可变类型约束可能会得到令人惊讶的结果。例如:declarefunctionfnBadconstTextendsstring〔〕(args:T):void;T仍旧是string〔〕因为readonly〔a,b,c〕没有赋值为string〔〕fnBad(〔a,b,c〕); 这里,T的推断候选值是readonly〔a,b,c〕,而readonly数组不能用于需要可变数组的地方。在这种情况下,推理回退到约束,数组被视为string〔〕,调用仍然成功进行。 更好的定义应该使用readonlystring〔〕:declarefunctionfnGoodconstTextendsreadonlystring〔〕(args:T):void;Tisreadonly〔a,b,c〕fnGood(〔a,b,c〕); 同样,要记住,const修饰符只影响在调用中编写的对象、数组和基本类型表达式的推断,所以不会(或不能)用const修饰的参数不会看到任何行为的变化:declarefunctionfnGoodconstTextendsreadonlystring〔〕(args:T):void;constarr〔a,b,c〕;Tisstillstring〔〕theconstmodifierhasnoeffectherefnGood(arr); 支持多个配置文件 当管理多个项目时,从基础配置文件tsconfig。Json继承会很有帮助。使用extends字段,用于从compilerOptions中复制字段。packagesfrontendsrctsconfig。json{compilerOptions:{extends:。。。。。。tsconfig。base。json,outDir:。。lib,。。。}} 然而,在某些情况下,您可能希望从多个配置文件扩展。例如,想象一下使用npm中的TypeScript基础配置文件。如果你希望所有项目都使用npm上tsconfigstrictest包中的选项,那么有一个简单的解决方案:从tsconfigstrictest扩展tsconfig。base。json:tsconfig。base。json{compilerOptions:{extends:tsconfigstrictesttsconfig。json,。。。}} 这在一定程度上是可行的。如果有项目不想使用tsconfigstrictest,就必须手动禁用该选项,或者创建一个不扩展tsconfigstrictest的单独版本的tsconfig。base。json。 为了在这里提供更多的灵活性,Typescript5。0现在允许extends字段接收多个条目。例如,在这个配置文件中:{compilerOptions:{extends:〔a,b,c〕}} 这样写有点像直接扩展c,其中c扩展b,b扩展a。如果任何字段冲突,则后一项获胜。 因此,在下面的例子中,strictNullChecks和noImplicitAny都在最终的tsconfig。json文件中启用了。tsconfig1。json{compilerOptions:{strictNullChecks:true}}tsconfig2。json{compilerOptions:{noImplicitAny:true}}tsconfig。json{compilerOptions:{extends:〔。tsconfig1。json,。tsconfig2。json〕},files:〔。index。ts〕} 作为另一个例子,我们可以用下面的方式重写原来的。packagesfrontendsrctsconfig。json{compilerOptions:{extends:〔tsconfigstrictesttsconfig。json,。。。。。。tsconfig。base。json〕,outDir:。。lib,。。。}} 有关更多细节,请阅读原始pullrequest的更多信息。所有枚举都是Union枚举 当TypeScript最初引入枚举时,它们只不过是一组具有相同类型的数值常量。enumE{Foo10,Bar20,} E。Foo和E。Bar唯一的特别之处在于它们可以赋值给任何期望类型为E的东西。除此之外,它们基本上都是数字。functiontakeValue(e:E){}takeValue(E。Foo);workstakeValue(123);error! 直到TypeScript2。0引入了枚举字面量类型,枚举才变得更加特殊。Enum字面量类型为每个枚举成员指定了自己的类型,并将枚举本身转换为每个成员类型的并集。它们还允许我们只引用枚举类型的一个子集,并缩小这些类型的范围。enumColor{Red,Orange,Yellow,Green,Blue,Indigo,Violet}每个枚举成员都有自己的类型,可以引用!typePrimaryColorColor。RedColor。GreenColor。Blue;functionisPrimaryColor(c:Color):CisPrimaryColor{缩小字面量类型可以捕获bugTypeScript在这里会报错,因为我们最终会比较Color。Color。从Red到Color。Green。我们本想使用,但不小心写了returncColor。RedcColor。GreencColor。Blue;} 给每个枚举成员指定自己的类型有一个问题,即这些类型在某种程度上与成员的实际值相关联。在某些情况下,这个值是不可能计算出来的例如,枚举成员可以通过函数调用进行初始化。enumE{BlahMath。random()} 每当TypeScript遇到这些问题时,它都会悄无声息地退出并使用旧的枚举策略。这意味着要放弃并集和字面量类型的所有优点。 TypeScript5。0通过为每个计算成员创建唯一的类型,设法将所有枚举转换为联合枚举。这意味着现在可以缩小所有枚举的范围,并将其成员作为类型引用。 有关此更改的更多详细信息,请参阅GitHub上的详细说明。moduleResolution打包 TypeScript4。7为其module和modulerresolution设置引入了node16和nodenext选项。这些选项的目的是为了更好地在Node。js中为ECMAScript模块建立精确的查找规则模型。然而,这种模式有许多其他工具没有真正实施的限制。 例如,在Node。js的ECMAScript模块中,任何相对导入都需要包含文件扩展名。entry。mjsimportasutilsfrom。utils;wrong需要包含文件扩展名importasutilsfrom。utils。mjs;works 在Node。js和浏览器中这样做是有原因的它使文件查找更快,并且对于简单的文件服务器工作得更好。但对于许多使用打包工具的开发人员来说,node16nodenext的设置很麻烦,因为打包工具没有这些限制。在某种程度上,node解析模式更适合任何使用打包工具的人。 但在某些方面,最初的node解析模式已经过时了。大多数现代打包工具融合了Node。js中的ECMAScript模块和CommonJS查找规则。例如,无扩展导入就像在CommonJS中一样正常工作,但当查看包的导出条件时,他们会更喜欢ECMAScript文件中的导入条件。 为了建模打包的工作方式,TypeScript现在引入了一个新的策略:modulerresolution打包。{compilerOptions:{target:esnext,moduleResolution:bundler}} 如果你正在使用现代的打包工具,比如Vite、esbuild、swc、Webpack、Parcel,以及其他实现了混合查找策略的工具,那么新的打包工具选项应该很适合你。 要了解有关modulerresolution打包工具的更多信息,请查看实现的拉取请求。解析自定义标志 JavaScript工具现在可以对混合解析规则建模,就像我们上面描述的打包模式一样。由于工具的支持可能略有不同,TypeScript5。0提供了启用或禁用一些功能的方法,这些功能可能与您的配置一起使用,也可能无法使用。allowImportingTsExtensions allowImportingTsExtensions允许TypeScript文件使用特定于TypeScript的扩展名(如。ts、。mts或。tsx)相互导入。 仅当启用noEmit或emitDeclarationOnly时,才允许使用此标志,因为这些导入路径在运行时无法在JavaScript输出文件中解析。这里的期望是你的解析器(例如打包、运行时或其他工具)将使这些在。ts文件之间的导入正常工作。resolvePackageJsonExports resolvePackageJsonExports强制TypeScript查询package的exports字段。如果它从nodemodules中的包中读取数据,则会读取Json文件。 在moduleResolution的node16、nodenext和bundler选项中,这个选项默认为true。resolvePackageJsonImports resolvePackageJsonImports强制TypeScript查询package的imports字段。当查找以开头的文件时,该文件的祖先目录包含package。json文件。 在moduleResolution的node16、nodenext和bundler选项中,这个选项默认为true。allowArbitraryExtensions 在TypeScript5。0中,当导入路径以一个不是已知的JavaScript或TypeScript文件扩展名的扩展名结束时,编译器将查找该路径形式为{filebasename}。d。{extension}。ts的声明文件。例如,如果你在打包项目中使用CSSloader,你可能想为这些样式表编写(或生成)声明文件:app。css。cookiebanner{display:none;}app。d。css。tsdeclareconstcss:{cookieBanner:string;};exportdefaultcss;App。tsximportstylesfrom。app。css;styles。cookieBanner;string 默认情况下,这个导入将引发一个错误,让你知道TypeScript不理解这个文件类型,你的运行时可能不支持导入它。但是,如果您已经配置了运行时或打包器来处理它,则可以使用新的allowArbitraryExtensions编译器选项来抑制错误。 请注意,在历史上,通常通过添加一个名为app。css。d。ts的声明文件而不是app。d。css。ts来实现类似的效果然而,这只是通过Node对CommonJS的require解析规则实现的。严格来说,前者是一个名为app。css。js的JavaScript文件的声明文件。因为相对文件导入需要包含Node对ESM支持的扩展,所以在我们的例子中,TypeScript会在moduleResolutionnode16或nodenext下的ESM文件中出错。 有关更多信息,请阅读此功能的建议及其相应的pullrequest。customConditions customConditions接受额外的条件列表,当TypeScript从package。json的〔exports〕或(https:nodejs。orgapipackages。htmlexports)或imports字段解析时,这些条件应该成功。这些条件将添加到解析器默认使用的任何现有条件中。 例如,当在tsconfig。conf中设置此字段时。Json格式如下:{compilerOptions:{target:es2022,moduleResolution:bundler,customConditions:〔mycondition〕}} 在包中引用exports或imports字段时。TypeScript将考虑mycondition条件。 因此,当使用以下package。json从一个包中导入时{。。。exports:{。:{mycondition:。foo。mjs,node:。bar。mjs,import:。baz。mjs,require:。biz。mjs}}} TypeScript将尝试查找与foo。mjs对应的文件。 这个字段只有在node16、nodenext和bundler选项下modulerresolution才有效verbatimModuleSyntax 默认情况下,TypeScript会执行导入省略(importelision)操作。基本上,如果你写import{Car}from。car;exportfunctiondrive(car:Car){。。。} TypeScript检测到你只使用了类型导入,并完全删除导入。输出的JavaScript代码可能类似于下面这样:exportfunctiondrive(car){。。。} 大多数情况下,这是很好的,因为如果Car不是从。Car导出的值,我们会得到一个运行时错误。 但对于某些边界情况,它确实增加了一层复杂性。例如,没有import。car这样的语句;完全放弃导入。这实际上对有没有副作用的模块有影响。 TypeScript的JavaScriptemit策略也有另外几层复杂性省略导入并不总是由如何使用导入驱动的它通常还会咨询如何声明值。因此,下面的代码是否像下面这样并不总是很清楚export{Car}from。car; 应保存或丢弃。如果Car使用类之类的东西声明,那么它可以保留在生成的JavaScript文件中。但是,如果Car仅被声明为类型别名或接口,那么JavaScript文件根本不应该导出Car。 虽然TypeScript可能能够根据来自跨文件的信息做出这些发送决策,但不是每个编译器都可以。 导入和导出的类型修饰符在这些情况下有一点帮助。我们可以明确指定import或export仅用于类型分析,并且可以在JavaScript文件中使用类型修饰符完全删除。这条语句可以在JS输出中完全删除importtypeascarfrom。car;在JS输出中可以去掉命名的importexportCarimport{typeCar}from。car;export{typeCar}from。car; 类型修饰符本身并不是很有用默认情况下,模块省略仍然会删除导入,并且没有任何东西迫使你区分类型和普通的导入和导出。因此,TypeScript有标志importsNotUsedAsValues以确保您使用类型修饰符,preserveValueImports以防止某些模块省略行为,以及isolatedModules以确保您的TypeScript代码在不同的编译器上工作。不幸的是,理解这3个标志的细节是困难的,并且仍然存在一些具有意外行为的边缘情况。 TypeScript5。0引入了一个名为verbatimModuleSyntax的新选项来简化这种情况。规则要简单得多没有类型修饰符的任何导入或导出都将保留。任何使用类型修饰符的元素都被完全删除。完全抹去importtype{A}froma;重写import{b}frombcd;import{b,typec,typed}frombcd;重写import{}fromxyz;import{typexyz}fromxyz; 有了这个新选项,所见即所得。 不过,当涉及到模块互操作时,这确实有一些含义。在这个标志下,当你的设置或文件扩展名意味着需要使用不同的模块系统时,ECMAScript的import和export函数不会被重写。相反,你会得到一个错误。如果你需要发出使用require和module的代码。导出时,你必须使用ES2015之前的TypeScript模块语法: 虽然这是一个限制,但它确实有助于使一些问题更加明显。例如,在package。json中忘记设置type字段是很常见。因此,开发人员会在不知不觉中开始编写CommonJS模块,而不是ES模块,给出令人惊讶的查找规则和JavaScript输出。这个新标志确保你有意使用不同的文件类型,因为它们的语法是不同的。 因为verbatimModuleSyntax比importsNotUsedAsValues和preserveValueImports提供了一个更一致的事实,所以这两个现有的标志被弃用了。支持foreexport类型 当TypeScript3。8引入纯类型导入时,新语法不允许从module导出或从module重新导出ns时导出。TypeScript5。0增加了对这两种形式的支持:modelsvehicles。tsexportclassSpaceship{。。。}modelsindex。tsexporttypeasvehiclesfrom。spaceship;main。tsimport{vehicles}from。models;functiontakeASpaceship(s:vehicles。Spaceship){okvehicles只在type位置使用}functionmakeASpaceship(){returnnewvehicles。Spaceship();vehicles不能用作值,因为它是使用exporttype导出的。} JSDoc中的satisfiesSupport TypeScript4。9引入了satisfaction操作符。它确保表达式的类型是兼容的,而不会影响类型本身。例如,让我们看看下面的代码:interfaceCompilerOptions{strict?:boolean;outDir?:string;。。。extends?:stringstring〔〕;}declarefunctionresolveConfig(configPath:string):CompilerOptions;letmyCompilerOptions{strict:true,outDir:。。lib,。。。extends:〔tsconfigstrictesttsconfig。json,。。。。。。tsconfig。base。json〕,}satisfiesCompilerOptions; 在这里,TypeScript知道myCompilerOptions。extends是用数组声明的因为while满足验证了对象的类型,它不会直接将其更改为CompilerOptions并丢失信息。如果我们想映射到extends,没问题。letinheritedConfigsmyCompilerOptions。extends。map(resolveConfig); 这对TypeScript用户很有帮助,但是很多人使用TypeScript使用JSDoc注释对JavaScript代码进行类型检查。这就是为什么TypeScript5。0支持一个名为satisfy的新JSDoc标签,它做的事情完全相同。 satisfy可以捕获类型不匹配:tschecktypedefCompilerOptionsprop{boolean}〔strict〕prop{string}〔outDir〕prop{stringstring〔〕}〔extends〕satisfies{CompilerOptions}letmyCompilerOptions{outdir:。。lib,oops!wemeantoutDir}; 但它将保留表达式的原始类型,允许我们在后面的代码中更精确地使用值。tschecktypedefCompilerOptionsprop{boolean}〔strict〕prop{string}〔outDir〕prop{stringstring〔〕}〔extends〕satisfies{CompilerOptions}letmyCompilerOptions{strict:true,outDir:。。lib,extends:〔tsconfigstrictesttsconfig。json,。。。。。。tsconfig。base。json〕,};letinheritedConfigsmyCompilerOptions。extends。map(resolveConfig); satisfies也可以在任何括号表达式中使用。我们可以这样写myCompilerOptions:letmyCompilerOptionssatisfies{CompilerOptions}({strict:true,outDir:。。lib,extends:〔tsconfigstrictesttsconfig。json,。。。。。。tsconfig。base。json〕,}); 为什么?好吧,当你深入到其他代码中时,比如函数调用,它通常更有意义。compileCode(satisfies{CompilerOptions}({。。。})); 此功能由OleksandrTarasiuk提供!JSDoc中的overloadSupport 在TypeScript中,你可以为函数指定重载。重载为我们提供了一种方式,可以使用不同的参数调用函数,并可能返回不同的结果。它们可以限制调用者实际可以如何使用我们的函数,并细化他们将返回的结果。Ouroverloads:functionprintValue(str:string):void;functionprintValue(num:number,maxFractionDigits?:number):void;Ourimplementation:functionprintValue(value:stringnumber,maximumFractionDigits?:number){if(typeofvaluenumber){constformatterIntl。NumberFormat(enUS,{maximumFractionDigits,});valueformatter。format(value);}console。log(value);} 这里我们说过,printValue的第一个参数要么是字符串,要么是数字。如果它接收一个数字,那么它可以接收第二个实参来确定我们可以打印多少个小数。 TypeScript5。0现在允许JSDoc使用新的overload标签来声明重载。每个带有overload标签的JSDoc注释都被视为下面函数声明的不同重载。tscheckoverloadparam{string}valuereturn{void}overloadparam{number}valueparam{number}〔maximumFractionDigits〕return{void}param{stringnumber}valueparam{number}〔maximumFractionDigits〕functionprintValue(value,maximumFractionDigits){if(typeofvaluenumber){constformatterIntl。NumberFormat(enUS,{maximumFractionDigits,});valueformatter。format(value);}console。log(value);} 现在,无论我们是使用TypeScript还是JavaScript文件编写,TypeScript都可以让我们知道我们是否错误地调用了函数。allallowedprintValue(hello!);printValue(123。45);printValue(123。45,2);printValue(hello!,123);error! 在build下传递特定的标志 TypeScript现在允许在build模式下传递以下标志declarationemitDeclarationOnlydeclarationMapsoureMapinlineSourceMap 这使得您可以更容易地自定义构建的某些部分,其中可能有不同的开发和生产构建。 例如,开发版本的库可能不需要生成声明文件,但生产版本需要。项目可以配置声明发射为默认关闭,并简单地进行构建tscbuildp。myprojectdir 完成内部循环后,生产构建只需传递declaration标志即可。tscbuildp。myprojectdirdeclaration ExhaustiveswitchcaseCompletions 当编写switch语句时,TypeScript现在会检测被检查的值何时具有文字类型。如果是这样,它将为每个case提供一个完整的框架。 速度、内存和包大小的优化 TypeScript5。0在我们的代码结构、数据结构和算法实现方面包含了许多强大的更改。这些都意味着你的整个体验应该更快不仅仅是运行TypeScript,甚至是安装它。 相对于TypeScript4。9,我们在速度和大小方面取得了一些有趣的胜利。 场景 相对于TS4。9时间或者大小 Materialui构建时间 90 Playwright构建时间 89 tsc启动时间 89 tsc构建时间 86 Outlook构建时间 83 VSCode构建时间 81 typescript打包大小 58 换句话说,我们发现TypeScript5。0Beta版构建VSCode所花费的时间仅为TypeScript4。9的81。 如何?有一些值得注意的改进,我们希望在未来提供更多的细节。但我们不会让你等待那篇博客文章。 首先,我们最近将TypeScript从命名空间迁移到模块,允许我们利用现代构建工具来执行作用域提升等优化。使用这个工具,重新审视我们的打包策略,并移除一些废弃的代码,已经从TypeScript4。9的63。8MB包大小中削减了约26。5MB。通过直接调用函数,它也显著地提高了速度。 TypeScript还为编译器中的内部对象类型增加了更多的一致性,同时也精简了某些对象类型。这减少了多态和大态的使用站点,同时抵消了一些作为权衡而来的内存占用。 我们还在将信息序列化为字符串时执行了一些缓存。类型显示,可能作为错误报告、声明发出、代码完成等的一部分发生,最终可能会非常昂贵。TypeScript现在缓存了一些常用的机制,以便在这些操作中重用。 总的来说,我们期望大多数代码库从TypeScript5。0开始速度都将得到提升,并始终能够重现10到20的优势。当然,这取决于硬件和代码库的特性,但我们鼓励您今天就在代码库中尝试一下! 运行时的要求 TypeScript现在以ECMAScript2018为目标。对于Node用户来说,这意味着最低版本要求至少是Node。js10及更高版本。lib。d。ts变更 DOM类型生成方式的改变可能会对现有代码产生影响。值得注意的是,某些属性已经从数值类型转换为数值字面量类型,处理cut、copy和paste事件的属性和方法也在不同接口之间进行了移动。API重大变化 在TypeScript5。0中,我们转向了模块,删除了一些不必要的接口,并对正确性进行了一些改进。有关更改的更多详细信息,请参阅我们的API重大更改页面。关系运算符中禁止的隐式强制操作 如果你编写的代码可能导致隐式的字符串到数字转换,TypeScript中的某些操作将会警告你:functionfunc(ns:numberstring){returnns4;Error,可能的隐式转换} 在5。0中,这也适用于关系操作符、、和:functionfunc(ns:numberstring){returnns4;错误} 如果需要,可以使用显式强制操作数为数字:functionfunc(ns:numberstring){returnns4;OK} 这种正确性的改进由Mateusz提供Burzyski。Enum改革 自TypeScript第一次发布以来,枚举一直存在一些奇怪的问题。在5。0中,我们清理了其中的一些问题,同时减少了理解可以声明的各种枚举所需的概念数量。 你可能会看到两个主要的新错误。第一,将域外字面量赋值给enum类型现在会出错,正如我们所料:enumSomeEvenDigit{Zero0,Two2,Four4}错误letm:SomeEvenDigit1; 另一个问题是,声明某种类型的间接字符串数字混合枚举形式会错误地创建一个全数字枚举:enumLetters{Aa}enumNumbers{one1,twoLetters。A}Nowcorrectlyanerrorconstt:numberNumbers。two; 在experimentalDecorators下为构造函数中的参数装饰器进行更准确的类型检查 TypeScript5。0使得experimentalDecorators下的装饰器的类型检查更加准确。在对构造函数参数使用装饰器时,这一点变得很明显。exportdeclareconstinject:(entity:any)(target:object,key:stringsymbol,index?:number)void;exportclassFoo{}exportclassC{constructor(inject(Foo)privatex:any){}} 这个调用将失败,因为key需要一个stringsymbol,但构造函数参数接收到的键是undefined。正确的修复方法是改变inject中的key类型。如果你使用的库无法升级,一个合理的解决方案是将inject包装在一个更类型安全的装饰器函数中,并对键使用类型断言。 弃用和默认更改 在TypeScript5。0中,我们已经弃用了以下设置和设置值:target:ES3outnoImplicitUseStrictkeyofStringsOnlysuppressExcessPropertyErrorssuppressImplicitAnyIndexErrorsnoStrictGenericCheckscharsetimportsNotUsedAsValuespreserveValueImportsprependinprojectreferences 这些配置将继续被允许,直到TypeScript5。5版本,那时它们将被完全删除,然而,如果你使用这些设置,你将收到一个警告。在TypeScript5。0以及未来的版本5。1、5。2、5。3和5。4中,你可以指定ignoreprecations:5。0来消除这些警告。我们还将很快发布一个4。9补丁,允许指定ignoreprecations以允许更顺利的升级。除了弃用之外,我们还更改了一些设置,以更好地改进TypeScript中的跨平台行为。 newLine,用于控制JavaScript文件中的行结尾,如果不指定,则根据当前操作系统推断。我们认为构建应该尽可能确定,Windows记事本现在支持换行行结束,所以新的默认设置是LF。旧的特定于操作系统的推理行为不再可用。 forceConsistentCasingInFileNames,这确保了在一个项目中所有对相同文件名的引用都同意使用大小写,现在默认为true。这可以帮助捕获在不区分大小写的文件系统上编写的代码差异问题。 您可以留下反馈并查看关于5。0弃用的跟踪问题的更多信息接下来是什么? TypeScript5。0正在成为一个伟大的版本。在接下来的几周里,我们将专注于bug修复、稳定性和优化即将发布的候选版本,然后是第一个稳定版本。 和往常一样,关于我们的发行版的细节(包括目标日期!)可以在TypeScript5。0迭代计划中找到。我们希望迭代计划使TypeScript5。0更容易围绕您和您的团队的时间表进行测试! 我们也希望TypeScript5。0Beta版能带来很多你期待已久的新功能。让我们的beta版本(或我们的夜间构建)今天尝试一下,让我们知道你的想法!