打字稿 4.4 测试版

2021-07-22 21:53:15

要开始使用测试版,您可以通过 NuGet 获取它,或者使用带有以下命令的 npm:在 JavaScript 中,我们经常需要以不同的方式探测变量,以查看它是否具有我们可以使用的更具体的类型。 TypeScript 理解这些检查并将它们称为类型保护。类型检查器利用称为控制流分析的东西来推断每种语言构造中的类型,而不是在我们每次使用变量时都让 TypeScript 相信它的类型。 function foo ( arg: unknown ) { if ( typeof arg === "string" ) { // 我们现在知道这是一个字符串。安慰 。日志(arg.toUpperCase());在这个例子中,我们检查了 arg 是否是一个字符串。 TypeScript 识别了 typeof arg === "string" 检查,它认为这是一种类型保护,并且能够确定 arg 应该是 if 块主体中的字符串。 function foo ( arg: unknown ) { const argIsString = typeof arg === "string" ;如果(argIsString){ 控制台。日志(arg.toUpperCase()); // ~~~~~~~~~~~ // 错误!类型“未知”上不存在属性“toUpperCase”。在以前的 TypeScript 版本中,这将是一个错误——即使 argIsString 被分配了类型保护的值,TypeScript 只是丢失了该信息。这很不幸,因为我们可能想在多个地方重复使用相同的检查。为了解决这个问题,用户通常不得不重复自己或使用类型断言(强制转换)。在 TypeScript 4.4 中,情况不再如此。上面的例子没有错误!当 TypeScript 看到我们正在测试一个常量值时,它会做一些额外的工作来查看它是否包含类型保护。如果该类型保护对常量、只读属性或未修改的参数进行操作,则 TypeScript 能够适当地缩小该值。

保留了不同类型的类型保护条件——不仅仅是 typeof 检查。例如,检查受歧视的工会就像一种魅力。输入形状 = | { kind: "circle" , radius: number } | { kind: "square" , sideLength: number } ;功能区(形状:形状):数字{ const isCircle = shape 。种类 === "圆圈" ; if ( isCircle ) { // 我们知道这里有一个圆!返回数学。 PI * 形状。半径 ** 2 ; } else { // 我们知道这里还剩下一个正方形!返回形状。边长 ** 2 ;再举一个例子,这里有一个函数检查它的两个输入是否有内容。 function doSomeChecks ( inputA: string | undefined , inputB: string | undefined , shouldDoExtraWork: boolean , ) { let mustDoWork = inputA && inputB && shouldDoExtraWork ; if ( mustDoWork ) { // 可以访问 'inputA' 和 'inputB' 的 'string' 属性! const upperA = inputA 。大写(); const upperB = inputB 。大写(); // ... } } 如果 mustDoWork 为真,TypeScript 可以理解 inputA 和 inputB 都存在。这意味着我们不必编写像 inputA 这样的非空断言!说服 TypeScript inputA 不是未定义的。这里的一个巧妙特点是这种分析是可传递的。如果我们将一个常量分配给其中包含更多常量的条件,并且这些常量每个都被分配了类型保护,那么 TypeScript 可以稍后传播这些条件。 function f ( x: string | number | boolean ) { const isString = typeof x === "string" ; const isNumber = typeof x === "number" ; const isStringOrNumber = isString || isNumber ; if ( isStringOrNumber ) { x ; // 'x' 的类型是 'string |数字'。 }其他{x; // 'x' 的类型是 'boolean'。 } }

请注意,有一个截止点——TypeScript 在检查这些条件时不会任意深入,但它的分析对于大多数检查来说已经足够深入。这个特性应该可以让很多直观的 JavaScript 代码在 TypeScript 中“正常工作”,而不会妨碍你。有关更多详细信息,请查看 GitHub 上的实现! TypeScript 允许我们使用索引签名来描述每个属性都必须具有特定类型的对象。这允许我们将这些对象用作类似字典的类型,我们可以在其中使用字符串键通过方括号对它们进行索引。例如,我们可以编写一个带有索引签名的类型,该类型接受字符串键并映射到布尔值。如果我们尝试分配布尔值以外的任何内容,我们将收到错误消息。 interface BooleanDictionary { [ key: string ]: boolean ;声明 let myDict: BooleanDictionary ; // 有效分配布尔值 myDict [ "foo" ] = true ; myDict [ "bar" ] = false ; // 错误,"oops" 不是布尔值 myDict [ "baz" ] = "oops" ;虽然 Map 在这里可能是更好的数据结构(特别是 Map<string, boolean>),但 JavaScript 对象通常更方便使用,或者恰好是我们可以使用的对象。类似地,Array<T>s 已经定义了一个数字索引签名,它允许我们插入/检索 T 类型的值。

// 这是 TypeScript 对内置数组类型定义的一部分。 interface Array < T > { [ index: number ]: T ; // ... } let arr = new Array < string > ( ) ; // 有效的 arr [ 0 ] = "hello!" ; // 错误,这里需要一个“字符串”值 arr [ 1 ] = 123 ;索引签名对于在野外表达大量代码非常有用;然而,到目前为止,它们仅限于字符串和数字键(并且字符串索引签名有一个故意的怪癖,它们可以接受数字键,因为无论如何它们都会被强制转换为字符串)。这意味着 TypeScript 不允许使用符号键索引对象。 TypeScript 也无法对某些字符串键子集的索引签名进行建模——例如,一个索引签名只描述名称以文本数据开头的属性。 TypeScript 4.4 解决了这些限制,并允许符号和模板字符串模式的索引签名。例如,TypeScript 现在允许我们声明可以在任意符号上键入的类型。界面颜色{ [符号:符号]:数字; } const red = Symbol("红色"); const green = Symbol ("绿色"); const blue = Symbol ("blue");让颜色:颜色 = { } ;颜色 [红色] = 255 ; // 允许赋值 let redVal = colors [ red ] ; // 'redVal' 的类型为 'number' 颜色 [ blue ] = "da ba dee" ; // 错误:类型 'string' 不能分配给类型 'number'。类似地,我们可以使用模板字符串模式类型编写索引签名。这样做的一种用途可能是从 TypeScript 的多余属性检查中免除以数据开头的属性。当我们将对象字面量传递给具有预期类型的​​内容时,TypeScript 将查找未在预期类型中声明的多余属性。接口选项 { 宽度?:数字;高度?:数字; } let a: Options = { width: 100 , height: 100 , "data-blah": true , // 错误! 'data-blah' 没有在 'Options' 中声明。 }; interface OptionsWithDataProps extends Options { // 允许任何以“data-”开头的属性。 [ optName: `data- ${string }` ]: 未知; } let b: OptionsWithDataProps = { width: 100 , height: 100 , "data-blah": true , // 有效! "unknown-property": true , // 错误! 'unknown-property' 未在 'OptionsWithDataProps' 中声明。 };

关于索引签名的最后一点是,它们现在允许联合类型,只要它们是无限域原始类型的联合——特别是:参数是这些类型联合的索引签名将脱糖为几个不同的索引签名。接口数据 { [ optName: string |符号]:任何; } // 等价于 interface Data { [ optName: string ]: any ; [ optName: 符号 ]: 任何 ;在 JavaScript 中,任何类型的值都可以用 throw 抛出并在 catch 子句中捕获。因此,TypeScript 过去将 catch 子句变量类型化为 any,并且不允许任何其他类型注释: try { // 谁知道这可能会抛出什么... executeSomeThirdPartyCode ( ) ; } catch ( err ) { // 错误:任何控制台。错误(错误。消息); // 允许,因为 'any' 错误。 thisWillProbablyFail(); // 允许,因为 'any' :( } 一旦 TypeScript 添加了未知类型,很明显,对于想要最高正确性和类型安全性的用户来说,unknown 是比 catch 子句变量中的 any 更好的选择,因为它缩小了更好并迫使我们针对任意值进行测试。最终,TypeScript 4.0 允许用户在每个 catch 子句变量上指定未知(或任何)的显式类型注释,以便我们可以根据具体情况选择更严格的类型;然而, 对于某些人来说,在每个 catch 子句上手动指定 : unknown 是一件苦差事。这就是为什么 TypeScript 4.4 引入了一个名为 --useUnknownInCatchVariables 的新标志。这个标志将 catch 子句变量的默认类型从 any 更改为 unknown。

尝试{executeSomeThirdPartyCode(); } catch ( err ) { // 错误:未知 // 错误!类型“未知”上不存在属性“消息”。安慰 。错误(错误。消息); // 有效!我们可以将“错误”从“未知”缩小到“错误”。如果(错误实例错误){控制台。错误(错误。消息);在 --strict 系列选项下启用此标志。这意味着如果您使用 --strict 检查代码,此选项将自动打开。您最终可能会在 TypeScript 4.4 中遇到错误,例如“未知”类型上不存在“消息”属性。类型“未知”上不存在属性“名称”。类型“未知”上不存在属性“堆栈”。如果我们不想在 catch 子句中处理未知变量,我们总是可以添加一个明确的 : any 注释,以便我们可以选择退出更严格的类型。在 JavaScript 中,读取对象上缺失的属性会产生 undefined 值。也可能有一个值为 undefined 的实际属性。 JavaScript 中的许多代码倾向于以相同的方式处理这些情况,因此最初 TypeScript 只是解释每个可选属性,就好像用户在类型中写入了 undefined 一样。例如,这意味着用户可以明确地用 undefined 代替年龄。 const p: Person = { name: "Daniel" , age: undefined , // 默认是可以的。 };

因此,默认情况下,TypeScript 不区分值为 undefined 的存在属性和缺失属性。虽然这在大多数情况下都有效,但并非所有 JavaScript 代码都做出相同的假设。 Object.assign、Object.keys、object spread ( { ...obj }) 和 for– in 循环等函数和运算符的行为取决于对象上是否实际存在属性。在我们的 Person 示例中,如果在其存在很重要的上下文中观察到 age 属性,则这可能会导致运行时错误。在 TypeScript 4.4 中,新标志 --exactOptionalPropertyTypes 指定可选属性类型应完全按照书面解释,这意味着 | undefined 未添加到类型中: // 使用 'exactOptionalPropertyTypes': const p: Person = { name: "Daniel" , age: undefined , // 错误! undefined 不是数字 } ;此标志不是 --strict 系列的一部分,如果您希望这种行为,则需要显式打开。它还需要启用 --strictNullChecks 。我们将对绝对类型和其他定义进行更新,以尝试使转换尽可能简单,但您可能会遇到一些摩擦,具体取决于您的代码结构。 TypeScript 的 --help 选项得到了更新!感谢 Song Gao 的部分工作,我们进行了更改以更新编译器选项的描述,并使用一些颜色和其他视觉分离重新设计 --help 菜单的样式。虽然我们仍在对我们的样式进行一些迭代,以便在平台默认主题上运行良好,但您可以通过查看原始提案线程来了解它的外观。 TypeScript 现在缓存内部符号是否可在不同上下文中访问,以及应如何打印特定类型。这些更改可以提高 TypeScript 在具有相当复杂类型的代码中的一般性能,尤其是在 --declaration 标志下发出 .d.ts 文件时。 TypeScript 通常必须对文件路径进行多种类型的“规范化”,以使它们成为编译器可以在任何地方使用的一致格式。这涉及诸如用斜杠替换反斜杠或删除路径的中间 /./ 和 /../ 段之类的事情。当 TypeScript 必须在数百万条路径上运行时,这些操作最终会有点慢。在 TypeScript 4.4 中,路径首先要进行快速检查,以查看它们是否首先需要任何规范化。这些改进共同将大型项目的项目加载时间减少了 5-10%,而在我们内部测试的大型项目中则显着减少。

TypeScript 现在缓存它构建路径映射的方式(使用 tsconfig.json 中的路径选项)。对于具有数百个映射的项目,减少是显着的。您可以看到有关更改本身的更多信息。在实际上是一个错误中,如果 --strict 处于启用状态,TypeScript 最终会在 --incremental 编译下重做类型检查工作。这导致许多构建就像关闭了 --incremental 一样缓慢。 TypeScript 4.4 修复了这个问题,但该更改也已向后移植到 TypeScript 4.3。 TypeScript 4.4 为超大输出文件的源映射生成添加了优化。在构建旧版本的 TypeScript 编译器时,这会导致发射时间减少约 8%。我们要向 David Michon 表示感谢,他提供了一个简单而干净的更改来实现这一性能的胜利。在项目引用上使用 --build 模式时,TypeScript 必须执行最新检查以确定需要重建哪些文件。 然而,当执行 --force 构建时,该信息无关紧要,因为每个项目依赖项都将被重建从头开始。在 TypeScript 4.4 中,--force 构建避免了那些不必要的步骤并开始完整构建。在此处查看有关更改的更多信息。 TypeScript 为 Visual Studio 和 Visual Studio Code 等编辑器中的 JavaScript 编辑体验提供支持。大多数情况下,TypeScript 试图避开 JavaScript 文件;然而,TypeScript 通常有很多信息来提出自信的建议,以及提出不太具有侵略性的建议的方法。这就是 TypeScript 现在在纯 JavaScript 文件中发出拼写建议的原因——那些没有 // @ts-check 或在关闭 checkJs 的项目中。这些都是一样的“你的意思是……?” TypeScript 文件已有的建议,现在它们以某种形式在所有 JavaScript 文件中可用。

这些拼写建议可以提供一个微妙的线索,表明您的代码是错误的。在测试此功能时,我们设法在现有代码中找到了一些错误! TypeScript 正在试验对嵌入文本的编辑器支持,这有助于在代码中内联显示有用的信息,例如参数名称。您可以将其视为一种友好的“幽灵文本”。此功能由王文禄构建,其拉取请求有更多详细信息。您可以在此处跟踪我们将该功能与 Visual Studio Code 集成的进度。与每个 TypeScript 版本一样,lib.d.ts 的声明(尤其是为 Web 上下文生成的声明)已更改。您可以查阅我们的已知 lib.dom.d.ts 更改列表以了解受影响的内容。从技术上讲,使用 --strict 标志运行的用户可能会看到有关 catch 变量未知的新错误,特别是如果现有代码假定只捕获了 Error 值。这通常会导致错误消息,例如:“未知”类型上不存在属性“消息”。类型“未知”上不存在属性“名称”。类型“未知”上不存在属性“堆栈”。为了解决这个问题,您可以专门添加运行时检查以确保抛出的类型与您的预期类型相匹配。否则,您可以只使用类型断言,向您的 catch 变量添加一个显式 : any ,或者关闭 --useUnknownInCatchVariables。

抽象类 C { 抽象道具 = 1 ; // ~~~~ // 属性“prop”不能有初始值设定项,因为它被标记为抽象。为了帮助您的团队计划尝试使用 TypeScript 4.4,您可以阅读 4.4 迭代计划。我们目前的目标是在 8 月中旬发布候选版本,并在 2021 年 8 月底发布稳定版本。从现在到我们的候选版本,我们的团队将努力解决已知问题并听取您的反馈,因此请下载我们今天发布了测试版,让我们听听您的想法!