范文健康探索娱乐情感热点
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

TypeScript5。0Beta来了

  今天,我们很高兴地宣布 TypeScript 5.0 的测试版发布!
  此版本带来了许多新功能,同时旨在使 TypeScript 更小、更简单、更快。 我们已经实现了新的装饰器标准、更好地支持 Node 和捆绑器中的 ESM 项目的功能、库作者控制泛型推理的新方法、扩展了我们的 JSDoc 功能、简化了配置,并进行了许多其他改进。
  虽然 5.0 版本包括正确性更改和对较少使用的标志的弃用,但我们相信大多数用户将拥有与以前版本类似的升级体验。
  要开始使用测试版,您可以通过 NuGet 获取它,或通过以下命令使用 npm: npm install typescript@beta装饰器
  装饰器是即将推出的 ECMAScript 功能,它允许我们以可重用的方式自定义类及其成员。
  让我们考虑以下代码: class Person {     name: string;     constructor(name: string) {         this.name = name;     }      greet() {         console.log(`Hello, my name is ${this.name}.`);     } }  const p = new Person("Ray"); p.greet();
  greet 在这里非常简单,但让我们想象它是更复杂的东西——也许它执行一些异步逻辑,它是递归的,它有副作用等等。不管你想象的是哪种,假设你调用console.log输出一些以帮助调试问候语。class Person {     name: string;     constructor(name: string) {         this.name = name;     }      greet() {         console.log("LOG: Entering method.");          console.log(`Hello, my name is ${this.name}.`);          console.log("LOG: Exiting method.")     } }
  这种模式相当常见。如果有一种方法可以让我们对每个方法都这样做,那就太好了!
  这就是装饰器的作用。我们可以编写一个名为loggedMethod的函数,如下所示: function loggedMethod(originalMethod: any, _context: any) {      function replacementMethod(this: any, ...args: any[]) {         console.log("LOG: Entering method.")         const result = originalMethod.call(this, ...args);         console.log("LOG: Exiting method.")         return result;     }      return replacementMethod; }
  "这些any是怎么回事?"
  耐心点——我们现在让事情变得简单,这样我们就可以专注于这个函数在做什么。注意,loggedMethod接受原始方法(originalMethod)并返回一个函数 记录一条"输入(Entering)……"的消息将this及其所有参数传递给原始方法记录"退出(Exiting)……"消息返回原始方法返回的值
  现在我们可以使用loggedMethod来装饰greet: class Person {     name: string;     constructor(name: string) {         this.name = name;     }      @loggedMethod     greet() {         console.log(`Hello, my name is ${this.name}.`);     } }  const p = new Person("Ray"); p.greet();  // Output: // //   LOG: Entering method. //   Hello, my name is Ray. //   LOG: Exiting method.
  我们只是在greet上面使用了loggedMethod作为装饰器——注意,我们把它写成了@loggedMethod。当我们这样做时,它会被target方法和一个context对象调用。因为loggedMethod返回了一个新函数,所以这个函数替换了greet的原始定义。
  loggedMethod定义了第二个参数。它被称为"上下文对象",它有一些关于修饰方法是如何声明的有用信息——比如它是#private成员或者是静态的,或者方法的名称是什么。让我们重写loggedMethod来利用这一点,并打印出被装饰的方法的名称。 function loggedMethod(originalMethod: any, context: ClassMethodDecoratorContext) {     const methodName = String(context.name);      function replacementMethod(this: any, ...args: any[]) {         console.log(`LOG: Entering method "${methodName}".`)         const result = originalMethod.call(this, ...args);         console.log(`LOG: Exiting method "${methodName}".`)         return result;     }      return replacementMethod; }
  我们现在使用context参数——它是loggedMethod中第一个类型比any和any[]更严格的参数。TypeScript提供了一个名为ClassMethodDecoratorContext的类型,它为方法装饰器接受的上下文对象建模。
  除了元数据,方法的上下文对象还有一个有用的函数addInitializer。这是一种挂接到构造函数开头的方式(如果使用静态方法,则挂接到类本身的初始化)。
  举个例子——在JavaScript中,经常会写如下的模式: class Person {     name: string;     constructor(name: string) {         this.name = name;          this.greet = this.greet.bind(this);     }      greet() {         console.log(`Hello, my name is ${this.name}.`);     } }
  或者,可以将greet声明为一个初始化为箭头函数。 class Person {     name: string;     constructor(name: string) {         this.name = name;     }      greet = () => {         console.log(`Hello, my name is ${this.name}.`);     }; }
  编写这段代码是为了确保当greet作为独立函数调用或作为回调函数传递时,不会重新绑定。 const greet = new Person("Ray").greet;  // 我们不想这里出错 greet();
  我们可以编写一个装饰器,使用addInitializer在构造函数中为我们调用bind。 function bound(originalMethod: any, context: ClassMethodDecoratorContext) {     const methodName = context.name;     if (context.private) {         throw new Error(`"bound" cannot decorate private properties like ${methodName as string}.`);     }     context.addInitializer(function () {         this[methodName] = this[methodName].bind(this);     }); }
  当Bound装饰一个方法时它不会返回任何东西,它会保留原来的方法。相反,它会在其他字段初始化之前添加逻辑。class Person {     name: string;     constructor(name: string) {         this.name = name;     }      @bound     @loggedMethod     greet() {         console.log(`Hello, my name is ${this.name}.`);     } }  const p = new Person("Ray"); const greet = p.greet;  // Works! greet();
  注意,我们用了两个装饰器——@bound和@loggedMethod。这些装饰是以"相反的顺序"运行的。也就是说,@loggedMethod修饰了原始方法greet, @bound修饰了@loggedMethod的结果。在这个例子中,这没有关系——但如果你的装饰器有副作用或期望某种顺序,则需要关注他们的顺序。
  同样值得注意的是——如果你更喜欢代码风格化,你可以将这些装饰器放在同一行。     @bound @loggedMethod greet() {         console.log(`Hello, my name is ${this.name}.`);     }
  可能不太明显的是,我们甚至可以创建返回装饰器函数的函数。这使得我们可以对最终的装饰器进行一些自定义。如果我们愿意,我们可以让loggedMethod返回一个装饰器,并自定义它记录消息的方式。 function loggedMethod(headMessage = "LOG:") {     return function actualDecorator(originalMethod: any, context: ClassMethodDecoratorContext) {         const methodName = String(context.name);          function replacementMethod(this: any, ...args: any[]) {             console.log(`${headMessage} Entering method "${methodName}".`)             const result = originalMethod.call(this, ...args);             console.log(`${headMessage} Exiting method "${methodName}".`)             return result;         }          return replacementMethod;     } }
  如果我们这样做,我们必须在使用loggedMethod作为装饰器之前调用它。然后,我们可以传入任何字符串作为输出到控制台的消息的前缀。 class Person {     name: string;     constructor(name: string) {         this.name = name;     }      @loggedMethod("")     greet() {         console.log(`Hello, my name is ${this.name}.`);     } }  const p = new Person("Ray"); p.greet();  // Output: // //    Entering method "greet". //   Hello, my name is Ray. //    Exiting method "greet".
  装饰器可不仅仅用于方法!它们可以用于属性/字段、getter、setter和自动访问器。甚至类本身也可以装饰成子类化和注册。
  有关所涉及更改的更多信息,您可以查看原始的pull request。 与实验性遗留装饰器的差异
  如果你已经使用TypeScript一段时间了,你可能会意识到它多年来一直支持"实验性"装饰器。虽然这些实验性装饰器非常有用,但它们模拟了装饰器提案的一个更旧的版本,并且总是需要一个称为——experimentalDecorators的可选编译器标志。任何在TypeScript中不使用此标志的尝试都会提示错误。
  实验性装饰器将在可预见的未来继续存在;但是,如果没有这个标志,装饰器将成为所有新代码的有效语法。在——experimentalDecorators之外,它们将被类型检查并以不同的方式发出。类型检查规则和emit有很大的不同,虽然可以编写装饰器来同时支持新旧装饰器行为,但任何现有的装饰器函数都不太可能这样做。
  这个新的装饰器提案与——emitDecoratorMetadata不兼容,而且它不允许装饰参数。未来的ECMAScript提案可能有助于弥合这一差距。
  最后说一句:目前,装饰器的提案要求类装饰器在export关键字之后(如果存在的话)。 export @register class Foo {     // ... }  export @Component({     // ... }) class Bar {     // ... }
  TypeScript会在JavaScript文件中强制执行此限制,但TypeScript文件不会这样做。这部分是由现有用户驱动的——我们希望在我们原始的"实验性"装饰器和标准化装饰器之间提供一个稍微简单的迁移路径。此外,我们从许多用户那里听到了对原始样式的偏好,我们希望在未来的标准讨论中真诚地讨论这个问题。 编写类型良好的装饰器
  上面的loggedMethod和bound装饰器示例很简单,并省略了大量关于类型的细节。
  输入装饰器可能相当复杂。例如,上面的loggedMethod类型良好的版本可能看起来像这样: function loggedMethod(     target: (this: This, ...args: Args) => Return,     context: ClassMethodDecoratorContext Return> ) {     const methodName = String(context.name);      function replacementMethod(this: This, ...args: Args): Return {         console.log(`LOG: Entering method "${methodName}".`)         const result = target.call(this, ...args);         console.log(`LOG: Exiting method "${methodName}".`)         return result;     }      return replacementMethod; }
  我们必须使用this、Args和return类型参数分别建模this、参数和原始方法的返回类型。
  具体定义装饰器函数的复杂程度取决于你想要什么。请记住,你的装饰器的使用次数将超过它们的编写次数,所以类型良好的版本通常是更好的——但显然需要与可读性权衡,所以请尽量保持简单。
  constType参数
  在推断对象的类型时,TypeScript通常会选择一种通用的类型。例如,在这个例子中,names的推断类型是string[]: type HasNames = { readonly names: string[] }; function getNamesExactly(arg: T): T["names"] {     return arg.names; }  // Inferred type: string[] const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});
  通常这样做的目的是实现突变。
  然而,根据getnames确切的作用以及它的使用方式,通常情况下需要更具体的类型。
  到目前为止,API作者通常不得不建议在某些地方添加const,以实现所需的推断: // 希望类型是: //    readonly ["Alice", "Bob", "Eve"] // 但类型变成: //    string[] const names1 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});  //正确获得想要的类型: //    readonly ["Alice", "Bob", "Eve"] const names2 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]} as const);
  这可能很麻烦,也很容易忘记。在TypeScript 5.0中,你现在可以在类型参数声明中添加const修饰符,以使const类推断成为默认值: type HasNames = { names: readonly string[] }; function getNamesExactly(arg: T): T["names"] { //                       ^^^^^     return arg.names; }  // Inferred type: readonly ["Alice", "Bob", "Eve"] // Note: Didn"t need to write "as const" here const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] });
  注意,const修饰符并不排斥可变值,也不需要不可变约束。使用可变类型约束可能会得到令人惊讶的结果。例如: declare function fnBad(args: T): void;  // "T" 仍旧是 "string[]" 因为 "readonly ["a", "b", "c"]" 没有赋值为 "string[]" fnBad(["a", "b" ,"c"]);
  这里,T的推断候选值是readonly ["a", "b", "c"],而readonly数组不能用于需要可变数组的地方。在这种情况下,推理回退到约束,数组被视为string[],调用仍然成功进行。
  更好的定义应该使用readonly string[]: declare function fnGood(args: T): void;  // T is readonly ["a", "b", "c"] fnGood(["a", "b" ,"c"]);
  同样,要记住,const修饰符只影响在调用中编写的对象、数组和基本类型表达式的推断,所以不会(或不能)用const修饰的参数不会看到任何行为的变化: declare function fnGood(args: T): void; const arr = ["a", "b" ,"c"];  // "T" is still "string[]"-- the "const" modifier has no effect here fnGood(arr);
  支持多个配置文件
  当管理多个项目时,从"基础"配置文件tsconfig.Json继承会很有帮助。使用extends字段,用于从compilerOptions中复制字段。 // packages/front-end/src/tsconfig.json {     "compilerOptions": {         "extends": "../../../tsconfig.base.json",          "outDir": "../lib",         // ...     } }
  然而,在某些情况下,您可能希望从多个配置文件扩展。例如,想象一下使用npm中的TypeScript基础配置文件。如果你希望所有项目都使用npm上@tsconfig/strictest包中的选项,那么有一个简单的解决方案:从@tsconfig/strictest扩展tsconfig.base.json: // tsconfig.base.json {     "compilerOptions": {         "extends": "@tsconfig/strictest/tsconfig.json",          // ...     } }
  这在一定程度上是可行的。如果有项目不想使用@tsconfig/strictest,就必须手动禁用该选项,或者创建一个不扩展@tsconfig/strictest的单独版本的tsconfig.base.json。
  为了在这里提供更多的灵活性,Typescript 5.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"] }
  作为另一个例子,我们可以用下面的方式重写原来的。 // packages/front-end/src/tsconfig.json {     "compilerOptions": {         "extends": ["@tsconfig/strictest/tsconfig.json", "../../../tsconfig.base.json"],          "outDir": "../lib",         // ...     } }
  有关更多细节,请阅读原始pull request的更多信息。 所有枚举都是Union枚举
  当TypeScript最初引入枚举时,它们只不过是一组具有相同类型的数值常量。 enum E {     Foo = 10,     Bar = 20, }
  E.Foo和E.Bar唯一的特别之处在于它们可以赋值给任何期望类型为E的东西。除此之外,它们基本上都是数字。 function takeValue(e: E) {}  takeValue(E.Foo); // works takeValue(123);   // error!
  直到TypeScript 2.0引入了枚举字面量类型,枚举才变得更加特殊。Enum字面量类型为每个枚举成员指定了自己的类型,并将枚举本身转换为每个成员类型的并集。它们还允许我们只引用枚举类型的一个子集,并缩小这些类型的范围。  enum Color {     Red, Orange, Yellow, Green, Blue, /* Indigo */, Violet }  // 每个枚举成员都有自己的类型,可以引用! type PrimaryColor = Color.Red | Color.Green | Color.Blue;  function isPrimaryColor(c: Color): C is PrimaryColor { //缩小字面量类型可以捕获bug // TypeScript在这里会报错,因为 //我们最终会比较` Color. Color `。从` Red `到` Color.Green `。 //我们本想使用||,但不小心写了&&     return c === Color.Red && c === Color.Green && c === Color.Blue; }
  给每个枚举成员指定自己的类型有一个问题,即这些类型在某种程度上与成员的实际值相关联。在某些情况下,这个值是不可能计算出来的——例如,枚举成员可以通过函数调用进行初始化。 enum E {     Blah = Math.random() }
  每当TypeScript遇到这些问题时,它都会悄无声息地退出并使用旧的枚举策略。这意味着要放弃并集和字面量类型的所有优点。
  TypeScript 5.0通过为每个计算成员创建唯一的类型,设法将所有枚举转换为联合枚举。这意味着现在可以缩小所有枚举的范围,并将其成员作为类型引用。
  有关此更改的更多详细信息,请参阅GitHub上的详细说明。 ——moduleResolution打包
  TypeScript 4.7为其——module和——modulerresolution设置引入了node16和nodenext选项。这些选项的目的是为了更好地在Node.js中为ECMAScript模块建立精确的查找规则模型。然而,这种模式有许多其他工具没有真正实施的限制。
  例如,在Node.js的ECMAScript模块中,任何相对导入都需要包含文件扩展名。 // entry.mjs import * as utils from "./utils";     //  wrong - 需要包含文件扩展名  import * as utils from "./utils.mjs"; //  works
  在Node.js和浏览器中这样做是有原因的——它使文件查找更快,并且对于简单的文件服务器工作得更好。但对于许多使用打包工具的开发人员来说,node16/nodenext的设置很麻烦,因为打包工具没有这些限制。在某种程度上,node解析模式更适合任何使用打包工具的人。
  但在某些方面,最初的node解析模式已经过时了。大多数现代打包工具融合了Node.js中的ECMAScript模块和CommonJS查找规则。例如,无扩展导入就像在CommonJS中一样正常工作,但当查看包的导出条件时,他们会更喜欢ECMAScript文件中的导入条件。
  为了建模打包的工作方式,TypeScript现在引入了一个新的策略:——modulerresolution打包。 {     "compilerOptions": {         "target": "esnext",         "moduleResolution": "bundler"     } }
  如果你正在使用现代的打包工具,比如Vite、esbuild、swc、Webpack、Parcel,以及其他实现了混合查找策略的工具,那么新的打包工具选项应该很适合你。
  要了解有关——modulerresolution打包工具的更多信息,请查看实现的拉取请求。 解析自定义标志
  JavaScript工具现在可以对"混合"解析规则建模,就像我们上面描述的打包模式一样。由于工具的支持可能略有不同,TypeScript 5.0提供了启用或禁用一些功能的方法,这些功能可能与您的配置一起使用,也可能无法使用。 allowImportingTsExtensions
  --allowImportingTsExtensions  允许TypeScript文件使用特定于TypeScript的扩展名(如.ts、.mts或.tsx)相互导入。
  仅当启用——noEmit或——emitDeclarationOnly时,才允许使用此标志,因为这些导入路径在运行时无法在JavaScript输出文件中解析。这里的期望是你的解析器(例如打包、运行时或其他工具)将使这些在.ts文件之间的导入正常工作。resolvePackageJsonExports
  ——resolvePackageJsonExports强制TypeScript查询package的exports字段。如果它从node_modules中的包中读取数据,则会读取Json文件。
  在——moduleResolution的node16、nodenext和bundler选项中,这个选项默认为true。resolvePackageJsonImports
  ——resolvePackageJsonImports 强制TypeScript查询package的imports字段。当查找以#开头的文件时,该文件的祖先目录包含package.json文件。
  在——moduleResolution的node16、nodenext和bundler选项中,这个选项默认为true。allowArbitraryExtensions
  在TypeScript 5.0中,当导入路径以一个不是已知的JavaScript或TypeScript文件扩展名的扩展名结束时,编译器将查找该路径形式为{file basename}.d.{extension}.ts的声明文件。例如,如果你在打包项目中使用CSS loader,你可能想为这些样式表编写(或生成)声明文件: /* app.css */ .cookie-banner {   display: none; } // app.d.css.ts declare const css: {   cookieBanner: string; }; export default css; // App.tsx import styles from "./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会在——moduleResolution node16或nodenext下的ESM文件中出错。
  有关更多信息,请阅读此功能的建议及其相应的pull request。 customConditions
  ——customConditions 接受额外的条件列表,当TypeScript从package.json的[exports]或(https://nodejs.org/api/packages.html#exports)或imports字段解析时,这些条件应该成功。这些条件将添加到解析器默认使用的任何现有条件中。
  例如,当在tsconfig. conf中设置此字段时。Json格式如下:{     "compilerOptions": {         "target": "es2022",         "moduleResolution": "bundler",         "customConditions": ["my-condition"]     } }
  在包中引用exports或imports字段时。TypeScript将考虑my-condition条件。
  因此,当使用以下package.json从一个包中导入时 {     // ...     "exports": {         ".": {             "my-condition": "./foo.mjs",             "node": "./bar.mjs",             "import": "./baz.mjs",             "require": "./biz.mjs"         }     } }
  TypeScript将尝试查找与foo.mjs对应的文件。
  这个字段只有在node16、nodenext和bundler选项下——modulerresolution才有效 --verbatimModuleSyntax
  默认情况下,TypeScript会执行导入省略(import elision)操作。基本上,如果你写 import { Car } from "./car";  export function drive(car: Car) {     // ... }
  TypeScript检测到你只使用了类型导入,并完全删除导入。输出的JavaScript代码可能类似于下面这样: export function drive(car) {     // ... }
  大多数情况下,这是很好的,因为如果Car不是从./ Car导出的值,我们会得到一个运行时错误。
  但对于某些边界情况,它确实增加了一层复杂性。例如,没有import "./car"这样的语句;-完全放弃导入。这实际上对有没有副作用的模块有影响。
  TypeScript的JavaScript emit策略也有另外几层复杂性——省略导入并不总是由如何使用导入驱动的——它通常还会咨询如何声明值。因此,下面的代码是否像下面这样并不总是很清楚 export { Car } from "./car";
  应保存或丢弃。如果Car使用类之类的东西声明,那么它可以保留在生成的JavaScript文件中。但是,如果Car仅被声明为类型别名或接口,那么JavaScript文件根本不应该导出Car。
  虽然TypeScript可能能够根据来自跨文件的信息做出这些发送决策,但不是每个编译器都可以。
  导入和导出的类型修饰符在这些情况下有一点帮助。我们可以明确指定import或export仅用于类型分析,并且可以在JavaScript文件中使用类型修饰符完全删除。 //这条语句可以在JS输出中完全删除 import type * as car from "./car";  // 在JS输出中可以去掉命名的import/export ` Car ` import { type Car } from "./car"; export { type Car } from "./car";
  类型修饰符 本身并不是很有用——默认情况下,模块省略仍然会删除导入,并且没有任何东西迫使你区分类型和普通的导入和导出。因此,TypeScript有标志——importsNotUsedAsValues以确保您使用类型修饰符,——preserveValueImports以防止某些模块省略行为,以及——isolatedModules以确保您的TypeScript代码在不同的编译器上工作。不幸的是,理解这3个标志的细节是困难的,并且仍然存在一些具有意外行为的边缘情况。
  TypeScript 5.0引入了一个名为——verbatimModuleSyntax的新选项来简化这种情况。规则要简单得多——没有类型修饰符的任何导入或导出都将保留。任何使用类型修饰符的元素都被完全删除。// 完全抹去 import type { A } from "a";  // 重写 "import { b } from "bcd";" import { b, type c, type d } from "bcd";  // 重写 "import {} from "xyz";" import { type xyz } from "xyz";
  有了这个新选项,所见即所得。
  不过,当涉及到模块互操作时,这确实有一些含义。在这个标志下,当你的设置或文件扩展名意味着需要使用不同的模块系统时,ECMAScript的import和export函数不会被重写。相反,你会得到一个错误。如果你需要发出使用require和module的代码。导出时,你必须使用ES2015之前的TypeScript模块语法:
  虽然这是一个限制,但它确实有助于使一些问题更加明显。例如,在package.json中忘记设置type字段是很常见。因此,开发人员会在不知不觉中开始编写CommonJS模块,而不是ES模块,给出令人惊讶的查找规则和JavaScript输出。这个新标志确保你有意使用不同的文件类型,因为它们的语法是不同的。
  因为——verbatimModuleSyntax比——importsNotUsedAsValues和——preserveValueImports提供了一个更一致的事实,所以这两个现有的标志被弃用了。 支持foreexport类型*
  当TypeScript 3.8引入纯类型导入时,新语法不允许从"module"导出*或从"module"重新导出ns时导出。TypeScript 5.0增加了对这两种形式的支持: // models/vehicles.ts export class Spaceship {   // ... }  // models/index.ts export type * as vehicles from "./spaceship";  // main.ts import { vehicles } from "./models";  function takeASpaceship(s: vehicles.Spaceship) {   //  ok -`vehicles`只在type位置使用 }  function makeASpaceship() {   return new vehicles.Spaceship();   //         ^^^^^^^^   // ` vehicles `不能用作值,因为它是使用` export type `导出的。 }
  JSDoc中的@satisfiesSupport
  TypeScript 4.9引入了satisfaction操作符。它确保表达式的类型是兼容的,而不会影响类型本身。例如,让我们看看下面的代码: interface CompilerOptions {     strict?: boolean;     outDir?: string;     // ...      extends?: string | string[]; }  declare function resolveConfig(configPath: string): CompilerOptions;  let myCompilerOptions = {     strict: true,     outDir: "../lib",     // ...      extends: [         "@tsconfig/strictest/tsconfig.json",         "../../../tsconfig.base.json"     ],  } satisfies CompilerOptions;
  在这里,TypeScript知道myCompilerOptions.extends是用数组声明的——因为while满足验证了对象的类型,它不会直接将其更改为CompilerOptions并丢失信息。如果我们想映射到extends,没问题。 let inheritedConfigs = myCompilerOptions.extends.map(resolveConfig);
  这对TypeScript用户很有帮助,但是很多人使用TypeScript使用JSDoc注释对JavaScript代码进行类型检查。这就是为什么TypeScript 5.0支持一个名为@ satisfy的新JSDoc标签,它做的事情完全相同。
  /** @ satisfy */可以捕获类型不匹配: // @ts-check  /**  * @typedef CompilerOptions  * @prop {boolean} [strict]  * @prop {string} [outDir]  * @prop {string | string[]} [extends]  */  /**  * @satisfies {CompilerOptions}  */ let myCompilerOptions = {     outdir: "../lib", //  ~~~~~~ oops! we meant outDir };
  但它将保留表达式的原始类型,允许我们在后面的代码中更精确地使用值。 // @ts-check  /**  * @typedef CompilerOptions  * @prop {boolean} [strict]  * @prop {string} [outDir]  * @prop {string | string[]} [extends]  */  /**  * @satisfies {CompilerOptions}  */ let myCompilerOptions = {     strict: true,     outDir: "../lib",     extends: [         "@tsconfig/strictest/tsconfig.json",         "../../../tsconfig.base.json"     ], };  let inheritedConfigs = myCompilerOptions.extends.map(resolveConfig);
  /** @satisfies */  也可以在任何括号表达式中使用。我们可以这样写myCompilerOptions:let myCompilerOptions = /** @satisfies {CompilerOptions} */ ({     strict: true,     outDir: "../lib",     extends: [         "@tsconfig/strictest/tsconfig.json",         "../../../tsconfig.base.json"     ], });
  为什么?好吧,当你深入到其他代码中时,比如函数调用,它通常更有意义。 compileCode(/** @satisfies {CompilerOptions} */ ({     // ... }));
  此功能由Oleksandr Tarasiuk提供! JSDoc中的@overloadSupport
  在TypeScript中,你可以为函数指定重载。重载为我们提供了一种方式,可以使用不同的参数调用函数,并可能返回不同的结果。它们可以限制调用者实际可以如何使用我们的函数,并细化他们将返回的结果。 // Our overloads: function printValue(str: string): void; function printValue(num: number, maxFractionDigits?: number): void;  // Our implementation: function printValue(value: string | number, maximumFractionDigits?: number) {     if (typeof value === "number") {         const formatter = Intl.NumberFormat("en-US", {             maximumFractionDigits,         });         value = formatter.format(value);     }      console.log(value); }
  这里我们说过,printValue的第一个参数要么是字符串,要么是数字。如果它接收一个数字,那么它可以接收第二个实参来确定我们可以打印多少个小数。
  TypeScript 5.0现在允许JSDoc使用新的@overload标签来声明重载。每个带有@overload标签的JSDoc注释都被视为下面函数声明的不同重载。 // @ts-check  /**  * @overload  * @param {string} value  * @return {void}  */  /**  * @overload  * @param {number} value  * @param {number} [maximumFractionDigits]  * @return {void}  */  /**  * @param {string | number} value  * @param {number} [maximumFractionDigits]  */ function printValue(value, maximumFractionDigits) {     if (typeof value === "number") {         const formatter = Intl.NumberFormat("en-US", {             maximumFractionDigits,         });         value = formatter.format(value);     }      console.log(value); }
  现在,无论我们是使用TypeScript还是JavaScript文件编写,TypeScript都可以让我们知道我们是否错误地调用了函数。 // all allowed printValue("hello!"); printValue(123.45); printValue(123.45, 2);  printValue("hello!", 123); // error!
  在—build下传递特定的标志
  TypeScript现在允许在——build模式下传递以下标志 --declaration --emitDeclarationOnly --declarationMap --soureMap --inlineSourceMap
  这使得您可以更容易地自定义构建的某些部分,其中可能有不同的开发和生产构建。
  例如,开发版本的库可能不需要生成声明文件,但生产版本需要。项目可以配置声明发射为默认关闭,并简单地进行构建 tsc --build -p ./my-project-dir
  完成内部循环后,"生产"构建只需传递——declaration标志即可。 tsc --build -p ./my-project-dir --declaration
  Exhaustiveswitch/caseCompletions
  当编写switch语句时,TypeScript现在会检测被检查的值何时具有文字类型。如果是这样,它将为每个case提供一个完整的框架。
  速度、内存和包大小的优化
  TypeScript 5.0在我们的代码结构、数据结构和算法实现方面包含了许多强大的更改。这些都意味着你的整个体验应该更快——不仅仅是运行TypeScript,甚至是安装它。
  相对于TypeScript 4.9,我们在速度和大小方面取得了一些有趣的胜利。
  场景
  相对于 TS 4.9时间或者大小
  Material-ui构建时间
  90%
  Playwright 构建时间
  89%
  tsc 启动时间
  89%
  tsc 构建时间
  86%
  Outlook 构建时间
  83%
  VS Code 构建时间
  81%
  typescript 打包大小
  58%
  换句话说,我们发现TypeScript 5.0 Beta版构建VS Code所花费的时间仅为TypeScript 4.9的81%。
  如何?有一些值得注意的改进,我们希望在未来提供更多的细节。但我们不会让你等待那篇博客文章。
  首先,我们最近将TypeScript从命名空间迁移到模块,允许我们利用现代构建工具来执行作用域提升等优化。使用这个工具,重新审视我们的打包策略,并移除一些废弃的代码,已经从TypeScript 4.9的63.8 MB包大小中削减了约26.5 MB。通过直接调用函数,它也显著地提高了速度。
  TypeScript还为编译器中的内部对象类型增加了更多的一致性,同时也精简了某些对象类型。这减少了多态和大态的使用站点,同时抵消了一些作为权衡而来的内存占用。
  我们还在将信息序列化为字符串时执行了一些缓存。类型显示,可能作为错误报告、声明发出、代码完成等的一部分发生,最终可能会非常昂贵。TypeScript现在缓存了一些常用的机制,以便在这些操作中重用。
  总的来说,我们期望大多数代码库从TypeScript 5.0开始速度都将得到提升,并始终能够重现10%到20%的优势。当然,这取决于硬件和代码库的特性,但我们鼓励您今天就在代码库中尝试一下!
  运行时的要求
  TypeScript现在以ECMAScript 2018为目标。对于Node用户来说,这意味着最低版本要求至少是Node.js 10及更高版本。 lib.d.ts 变更
  DOM类型生成方式的改变可能会对现有代码产生影响。值得注意的是,某些属性已经从数值类型转换为数值字面量类型,处理cut、copy和paste事件的属性和方法也在不同接口之间进行了移动。 API重大变化
  在TypeScript 5.0中,我们转向了模块,删除了一些不必要的接口,并对正确性进行了一些改进。有关更改的更多详细信息,请参阅我们的API重大更改页面。 关系运算符中禁止的隐式强制操作
  如果你编写的代码可能导致隐式的字符串到数字转换,TypeScript中的某些操作将会警告你: function func(ns: number | string) {   return ns * 4; // Error, 可能的隐式转换 }
  在5.0中,这也适用于关系操作符>、<、<=和>=: function func(ns: number | string) {   return ns > 4; //错误 }
  如果需要,可以使用+显式强制操作数为数字: function func(ns: number | string) {   return +ns > 4; // OK }
  这种正确性的改进由Mateusz提供Burzyński。 Enum改革
  自TypeScript第一次发布以来,枚举一直存在一些奇怪的问题。在5.0中,我们清理了其中的一些问题,同时减少了理解可以声明的各种枚举所需的概念数量。
  你可能会看到两个主要的新错误。第一,将域外字面量赋值给enum类型现在会出错,正如我们所料: enum SomeEvenDigit {     Zero = 0,     Two = 2,     Four = 4 }  // 错误 let m: SomeEvenDigit = 1;
  另一个问题是,声明某种类型的间接字符串/数字混合枚举形式会错误地创建一个全数字枚举: enum Letters {     A = "a" } enum Numbers {     one = 1,     two = Letters.A }  // Now correctly an error const t: number = Numbers.two;
  在experimentalDecorators下为构造函数中的参数装饰器进行更准确的类型检查
  TypeScript 5.0使得——experimentalDecorators下的装饰器的类型检查更加准确。在对构造函数参数使用装饰器时,这一点变得很明显。 export declare const inject:   (entity: any) =>     (target: object, key: string | symbol, index?: number) => void;  export class Foo {}  export class C {     constructor(@inject(Foo) private x: any) {     } }
  这个调用将失败,因为key需要一个string|symbol,但构造函数参数接收到的键是undefined。正确的修复方法是改变inject中的key类型。如果你使用的库无法升级,一个合理的解决方案是将inject包装在一个更类型安全的装饰器函数中,并对键使用类型断言。
  弃用和默认更改
  在TypeScript 5.0中,我们已经弃用了以下设置和设置值: --target: ES3 --out --noImplicitUseStrict --keyofStringsOnly --suppressExcessPropertyErrors --suppressImplicitAnyIndexErrors --noStrictGenericChecks --charset --importsNotUsedAsValues --preserveValueImports prepend  in project references
  这些配置将继续被允许,直到TypeScript 5.5版本,那时它们将被完全删除,然而,如果你使用这些设置,你将收到一个警告。在TypeScript 5.0以及未来的版本5.1、5.2、5.3和5.4中,你可以指定" ignoreprecations ": "5.0"来消除这些警告。我们还将很快发布一个4.9补丁,允许指定ignoreprecations以允许更顺利的升级。除了弃用之外,我们还更改了一些设置,以更好地改进TypeScript中的跨平台行为。
  ——newLine,用于控制JavaScript文件中的行结尾,如果不指定,则根据当前操作系统推断。我们认为构建应该尽可能确定,Windows记事本现在支持换行行结束,所以新的默认设置是LF。旧的特定于操作系统的推理行为不再可用。
  ——forceConsistentCasingInFileNames,这确保了在一个项目中所有对相同文件名的引用都同意使用大小写,现在默认为true。这可以帮助捕获在不区分大小写的文件系统上编写的代码差异问题。
  您可以留下反馈并查看关于5.0弃用的跟踪问题的更多信息 接下来是什么?
  TypeScript 5.0正在成为一个伟大的版本。在接下来的几周里,我们将专注于bug修复、稳定性和优化即将发布的候选版本,然后是第一个稳定版本。
  和往常一样,关于我们的发行版的细节(包括目标日期!)可以在TypeScript 5.0迭代计划中找到。我们希望迭代计划使TypeScript 5.0更容易围绕您和您的团队的时间表进行测试!
  我们也希望TypeScript 5.0 Beta版能带来很多你期待已久的新功能。让我们的beta版本(或我们的夜间构建)今天尝试一下,让我们知道你的想法!

交河故城,大地上雕刻出来的城市文卞文志中国西部的古城遗址沉淀了厚重的岁月沧桑,它们是一段段历史的标签,更是古代中原王朝与西域文化经贸交流的直接见证。在这其中,交河故城就是具有非凡意义的一座经典城墟。它自创建至今投票啦!柳州这条超美沿江路藏不住了,可骑行可赏景,值得你一票!2022年度十大最美农村路推选活动火热进行中柳州市柳城县柳城美丽沿江路正在参加9月份美好幸福路主题推选展示让咱们一起云赏一路美景别忘了投票哦柳州市柳城县柳城美丽沿江路柳城美丽沿江路想念你拉法山的红叶每到秋天,我就会想起那年那个秋天,想起拉法山,想起拉法山的红叶。2016年的秋天,突然想来一次说走就走的旅行。那就去看红叶吧。在今日头条上看到吉林有个红叶谷。于是决定去吉林看红叶。石榴不能随便吃?医生这几类人平时尽量少食用,建议了解石榴可以说是秋天的颜值担当,是一款内外兼修的水果,在我国石榴可不仅仅美味,同时也有多子多福的寓意。很多人会在家中种植一颗石榴树,这样不仅吃着方便,而且也代表着美好的寓意,石榴营养价你还在苦恼,总是怀不上孩子?4个原因,不妨让医生帮你排查一下老公,又是一条杠李女士已经与丈夫结婚三年多,新婚之后两人打算过两年二人世界,所以一直刻意避孕。但是因为近期长辈的不断催生,加上周围朋友陆续怀孕生子,李女士和丈夫也开始了备孕之路。可大衣哥准儿媳疑似怀孕,辞职后去医院做孕检,自曝婚礼进入倒计时01。hr先上船后买票,这是农村婚礼里常见的一幕,说白了就是奉子成婚!这种做法呢,看上去有些不道德,有点以孩子绑架女方父母的味道,但其实呢,这是保证年轻人婚姻长久的办法之一!02。为缩小鼻翼,小S二女儿迷上夹鼻器,医生曝后果严重恐鼻组织坏死小S(徐熙娣)2005年和许雅钧结婚后,接连生下三个漂亮女儿。她曾在播客节目老娘的老娘中透露,正值青春期的14岁二女儿Lily(许韶恩),准备长大后要去隆鼻。对此,小S表示反对。哪怀孕8个月女子肚里大量牛奶血,医生紧急提醒管住嘴32岁的西安孕妇王丹(化名)最近的经历可有些惊悚,因为爱吃火锅重口味,她在怀孕8个月竟进了医院ICU,幸亏医生当机立断进行剖宫产,才平安生下了二胎宝宝。就喜欢火锅的味儿一周不吃两次隔夜菜不能吃了?医生建议除了这3种,其它的或可以放心吃邻居王大妈说,隔夜的饭菜千万别吃,看上去还能吃的隔夜菜,可能味道还行,但是其中含有大量的亚硝酸盐,亚硝酸盐是致癌物,会引发胃癌。而且王大妈还表示,隔夜菜中的细菌可以通过加热的方式杀妇产科医生经常吃这3种食物,降压减脂延缓子宫衰老怎么可能?我那么健康怎么会卵巢早衰呢?王女士今年40岁了,明明还没有进入更年期年龄范围,却已经出现了更年期症状,卵巢和子宫功能大不如前,不仅脾气变得越来越暴躁,就连月经也变得不稳定湖人重签施罗德,苏群老师很开心,他曾怒怼杠精恶心就去医院文篮郭先生欧锦赛半决赛的比赛继续进行,西班牙男篮大战德国男篮,最终,西班牙以9691击败德国,晋级决赛。数据方面,德国的施罗德拿下30分1篮板8助攻2抢断,小瓦格纳15分,奥布莱斯
爸爸带娃技巧一切行动听妈妈指挥在养娃的道路上,你和队友分工合作吗?一起听听来自丁香妈妈星球的宝妈Honey。胖的故事吧。自打有娃以来,一路目睹着队友从没什么感觉到我觉得我的娃最可爱的心路历程,也见证了他从一个凡孕妈妈能喝咖啡吗?太太是一个重度咖啡爱好者,每天至少12杯。那么怀孕了究竟还能不能喝咖啡,有哪些注意事项呢?做了如下功课。一为什么很多人不建议孕妇和咖啡1。在孕期,准妈妈的身体清除咖啡因的能力降低。叶倩文再唱经典回味旧时友谊,曾和王菲同台炫技,和梅艳芳情意深叶倩文与李玟周笔畅在综艺节目声生不息中合作的一曲我要你的爱斩获了很多人的心,60岁的叶倩文依旧宝刀未老,跟比自己年龄小的人同台也毫不逊色,这首歌也勾起了许多人对于她和梅艳芳合作版本费翔妈妈毕丽娜亲手拆散儿子的姻缘,我离婚影响了他的一生文醒醒编辑小情书我不打算结婚,但我的感情生活很丰富。费翔作为曾经影响一代人的歌手和演员,中美混血的他五官精致,身姿挺拔,简直是万千少女心中的梦。但就是这样一个才貌双全的男人现如今已离婚才1年,赵丽颖和冯绍峰的生活相差得有点大相爱的时候以为余生都是你,而这个期限只维持了两年。娱乐圈里面有很多明星夫妻,他们在娱乐圈摸爬滚打,大多都是因为工作相识。赵丽颖和冯绍峰就是这样,当初他们带火了官宣体,他们的恋情公布48岁苏有朋再上热搜,近照曝光人设崩塌?作者丨酱姨这是酱姨陪你的第1499个夜晚最近,苏有朋受邀参加了综艺节目了不起舞社,并在节目中担任评委。这也是他时隔两年,重新回到荧幕,再次出现在人们眼前。因此有许多网友都很期待他在20102020年的NBA总冠军,哪一个的季后赛之旅最为精彩?众所周知,NBA是一个充满奇迹与激情的篮球舞台,而季后赛的比赛,则是更加精彩的对决。在这10年里,有多支球队登上这最高的荣誉宝座,其中不乏有极其精彩的对决。今天我们就来看看这10年35岁球星终于拿到首个冠军!曾在中超被称水货,失去后才知重要性在土耳其超级联赛的尾声,特拉布宗体育夺得了冠军的荣誉。这支球队有着浓厚的中超元素,阵中拥有曾在河北华夏幸福外援热尔维尼奥原武汉卓尔外援埃弗拉,和原大连一方外援哈姆西克。这个冠军对特4个赛季,帮助球队拿到3个冠军,想要继续留队,球迷表示一言难尽一我当然想继续为广东队效力,我想在这里退役。但从商业角度看,我们都知道,很多事情都是自然而然发生的。除了球员个人的意愿之外,球队在管理层面的想法也要考虑。很多事情总是不能如你所愿。10,大冷门!英超鱼腩爆发,22控球掀翻欧冠冠军,有望创奇迹本赛季,埃弗顿在英超发挥不佳,沦为了鱼腩队,兰帕德肩负重任,需要带队完成保级的任务。最近一段时间,埃弗顿遭遇魔鬼赛程,原本以为他们会一波崩盘,没想到却能绝境爆发,2次爆出了大冷门!四十不惑的年纪,我才知道我的贵人是谁大家好!我是安安妈妈。一转眼都快四十岁了,我也到了四十不惑的年龄。这之前我遇到过两位贵人,我却不知道。我的第一个坎,就是我失去了我的第一个孩子。我在28岁的时候怀了我的第一个孩子,