改进了交叉减少、联合兼容性和范围缩小
🌐 Improved Intersection Reduction, Union Compatibility, and Narrowing
TypeScript 4.8 在 --strictNullChecks 下带来了一系列的正确性和一致性改进。这些变化影响了交叉类型和联合类型的工作方式,并且被用于 TypeScript 缩小类型的方式中。
🌐 TypeScript 4.8 brings a series of correctness and consistency improvements under --strictNullChecks.
These changes affect how intersection and union types work, and are leveraged in how TypeScript narrows types.
例如,unknown 在精神上与联合类型 {} | null | undefined 接近,因为它接受 null、undefined 以及其他任何类型。TypeScript 现在可以识别这一点,并允许从 unknown 到 {} | null | undefined 的赋值。
🌐 For example, unknown is close in spirit to the union type {} | null | undefined because it accepts null, undefined, and any other type.
TypeScript now recognizes this, and allows assignments from unknown to {} | null | undefined.
tsfunction f(x: unknown, y: {} | null | undefined) {x = y; // always workedy = x; // used to error, now works}
另一个变化是,{} 与任何其他对象类型的交集会直接简化为该对象类型。这意味着我们能够将 NonNullable 重写为仅使用与 {} 的交集,因为 {} & null 和 {} & undefined 会被直接抛弃。
🌐 Another change is that {} intersected with any other object type simplifies right down to that object type.
That meant that we were able to rewrite NonNullable to just use an intersection with {}, because {} & null and {} & undefined just get tossed away.
diff- type NonNullable<T> = T extends null | undefined ? never : T;+ type NonNullable<T> = T & {};
这是一个改进,因为像这样的交叉类型可以被简化和赋值,而条件类型目前无法做到。因此,NonNullable<NonNullable<T>> 现在至少可以简化为 NonNullable<T>,而以前则不行。
tsfunction foo<T>(x: NonNullable<T>, y: NonNullable<NonNullable<T>>) {x = y; // always workedy = x; // used to error, now works}
这些变化还使我们能够在控制流分析和类型缩小方面引入合理的改进。例如,unknown 现在在真值分支中就像 {} | null | undefined 一样被缩小。
🌐 These changes also allowed us to bring in sensible improvements in control flow analysis and type narrowing.
For example, unknown is now narrowed just like {} | null | undefined in truthy branches.
tsfunction narrowUnknownishUnion(x: {} | null | undefined) {if (x) {x; // {}}else {x; // {} | null | undefined}}function narrowUnknown(x: unknown) {if (x) {x; // used to be 'unknown', now '{}'}else {x; // unknown}}
泛型值也会以类似的方式被收窄。
在检查一个值不是 null 或 undefined 时,TypeScript 现在只是将其与 {} 交集——这同样意味着它是 NonNullable。
将这里的许多更改结合起来,我们现在可以在不使用任何类型断言的情况下定义以下函数。
🌐 Generic values also get narrowed similarly.
When checking that a value isn’t null or undefined, TypeScript now just intersects it with {} - which again, is the same as saying it’s NonNullable.
Putting many of the changes here together, we can now define the following function without any type assertions.
tsfunction throwIfNullable<T>(value: T): NonNullable<T> {if (value === undefined || value === null) {throw Error("Nullable value!");}// Used to fail because 'T' was not assignable to 'NonNullable<T>'.// Now narrows to 'T & {}' and succeeds because that's just 'NonNullable<T>'.return value;}
value 现在被缩小为 T & {},并且现在与 NonNullable<T> 相同——所以函数的主体只是正常工作,没有任何 TypeScript 特有的语法。
单看这些改变,它们可能显得微不足道——但它们修复了多年来报告的无数小问题。
🌐 On their own, these changes may appear small - but they represent fixes for many many paper cuts that have been reported over several years.
有关这些改进的更多详情,你可以在这里 阅读更多。
🌐 For more specifics on these improvements, you can read more here.
模板字符串类型中 infer 类型的推断改进
🌐 Improved Inference for infer Types in Template String Types
TypeScript 最近引入了一种方法,可以在条件类型中向 infer 类型变量添加 extends 约束。
🌐 TypeScript recently introduced a way to add extends constraints to infer type variables in conditional types.
ts// Grabs the first element of a tuple if it's assignable to 'number',// and returns 'never' if it can't find one.type TryGetNumberIfFirst<T> =T extends [infer U extends number, ...unknown[]] ? U : never;
如果这些 infer 类型出现在模板字符串类型中并受限于基本类型,TypeScript 现在将尝试解析出字面量类型。
🌐 If these infer types appear in a template string type and are constrained to a primitive type, TypeScript will now try to parse out a literal type.
ts// SomeNum used to be 'number'; now it's '100'.type SomeNum = "100" extends `${infer U extends number}` ? U : never;// SomeBigInt used to be 'bigint'; now it's '100n'.type SomeBigInt = "100" extends `${infer U extends bigint}` ? U : never;// SomeBool used to be 'boolean'; now it's 'true'.type SomeBool = "true" extends `${infer U extends boolean}` ? U : never;
这现在可以更好地传达库在运行时将执行的操作,并提供更精确的类型。
🌐 This can now better convey what a library will do at runtime, and give more precise types.
关于这一点需要注意的是,当 TypeScript 解析这些字面量类型时,它会贪婪地尝试解析出看起来符合相应原始类型的尽可能多的内容;然而,它随后会检查该原始类型的输出是否与字符串内容匹配。换句话说,TypeScript 会检查从字符串到原始类型再回到字符串的过程是否一致。如果它发现字符串不能被“往返转换”,那么就会退回到基础原始类型。
🌐 One note on this is that when TypeScript parses these literal types out it will greedily try to parse out as much of what looks like of the appropriate primitive type; however it then checks to see if the print-back of that primitive matches up with the string contents. In other words, TypeScript checks whether the going from the string, to the primitive, and back matches. If it doesn’t see that the string can be “round-tripped”, then it will fall back to the base primitive type.
ts// JustNumber is `number` here because TypeScript parses out `"1.0"`, but `String(Number("1.0"))` is `"1"` and doesn't match.type JustNumber = "1.0" extends `${infer T extends number}` ? T : never;
你可以在这里查看更多关于此功能的信息。
🌐 You can see more about this feature here.
--build、--watch 和 --incremental 性能改进
🌐 --build, --watch, and --incremental Performance Improvements
TypeScript 4.8 引入了多项优化,应能加快 --watch 和 --incremental 相关的场景,以及使用 --build 的项目引用构建。例如,现在在 --watch 模式下 TypeScript 可以避免在无操作更改时浪费时间更新时间戳,这使得重建更快,并避免影响可能正在监视 TypeScript 输出的其他构建工具。同时,我们还引入了许多其他优化,能够在 --build、--watch 和 --incremental 之间重用信息。
🌐 TypeScript 4.8 introduces several optimizations that should speed up scenarios around --watch and --incremental, along with project references builds using --build.
For example, TypeScript is now able to avoid spending time updating timestamps during no-op changes in --watch mode, which makes rebuilds faster and avoids messing with other build tools that might be watching for TypeScript’s output.
Many other optimizations where we’re able to reuse information across --build, --watch, and --incremental have been introduced as well.
这些改进有多大? 在一个相当大的内部代码库上,我们在许多简单的常见操作中看到了大约10%-25%的时间减少,在未更改的场景下,时间减少约为40%。 我们在TypeScript代码库上也看到了类似的结果。
🌐 How big are these improvements? Well, on a fairly large internal codebase, we’ve seen time reductions on the order of 10%-25% on many simple common operations, with around 40% time reductions in no-change scenarios. We’ve seen similar results on the TypeScript codebase as well.
你可以在 GitHub 上查看更改以及性能结果。
🌐 You can see the changes, along with the performance results on GitHub.
比较对象和数组字面量时的错误
🌐 Errors When Comparing Object and Array Literals
在许多语言中,像 == 这样的运算符执行所谓的对象“值”相等。
例如,在 Python 中,使用 == 检查一个列表是否为空(通过检查其值是否等于空列表)是有效的。
🌐 In many languages, operators like == perform what’s called “value” equality on objects.
For example, in Python it’s valid to check whether a list is empty by checking whether a value is equal to the empty list using ==.
pyif people_at_home == []:print("here's where I lie, broken inside. </3")adopt_animals()
在 JavaScript 中情况并非如此,对象之间(因此也包括数组)使用 == 和 === 时,检查的是两个引用是否指向同一个值。我们认为,在 JavaScript 中类似的代码充其量只是对开发者的一个潜在陷阱,最坏情况下可能会在生产代码中导致错误。这就是为什么 TypeScript 现在不允许如下的代码。
🌐 This is not the case in JavaScript, where == and === between objects (and therefore, arrays) check whether both references point to the same value.
We believe that similar code in JavaScript is at best an early foot-gun for JavaScript developers, and at worst a bug in production code.
That’s why TypeScript now disallows code like the following.
tsif (peopleAtHome === []) {// ~~~~~~~~~~~~~~~~~~~// This condition will always return 'false' since JavaScript compares objects by reference, not value.console.log("here's where I lie, broken inside. </3")adoptAnimals();}
我们想向Jack Works表示感谢,他为这次检查作出了贡献。你可以在这里查看相关更改。
🌐 We’d like to extend our gratitude to Jack Works who contributed this check. You can view the changes involved here.
改进了绑定模式的推断
🌐 Improved Inference from Binding Patterns
在某些情况下,TypeScript 会从绑定模式中选择一个类型以进行更好的推断。
🌐 In some cases, TypeScript will pick up a type from a binding pattern to make better inferences.
tsdeclare function chooseRandomly<T>(x: T, y: T): T;let [a, b, c] = chooseRandomly([42, true, "hi!"], [0, false, "bye!"]);// ^ ^ ^// | | |// | | string// | |// | boolean// |// number
当 chooseRandomly 需要为 T 推断类型时,它主要会查看 [42, true, "hi!"] 和 [0, false, "bye!"];但是 TypeScript 需要确定这两种类型是应该为 Array<number | boolean | string> 还是元组类型 [number, boolean, string]。为此,它会寻找现有的候选类型作为提示,以查看是否存在元组类型。当 TypeScript 看到绑定模式 [a, b, c] 时,它会创建类型 [any, any, any],该类型会作为 T 的低优先级候选类型,同时也会作为 [42, true, "hi!"] 和 [0, false, "bye!"] 类型的提示使用。
🌐 When chooseRandomly needs to figure out a type for T, it will primarily look at [42, true, "hi!"] and [0, false, "bye!"];
but TypeScript needs to figure out whether those two types should be Array<number | boolean | string> or the tuple type [number, boolean, string].
To do that, it will look for existing candidates as a hint to see whether there are any tuple types.
When TypeScript sees the binding pattern [a, b, c], it creates the type [any, any, any], and that type gets picked up as a low-priority candidate for T which also gets used as a hint for the types of [42, true, "hi!"] and [0, false, "bye!"].
你可以看到这对 chooseRandomly 是有好处的,但在其他情况下却不够理想。例如,看看以下代码
🌐 You can see how this was good for chooseRandomly, but it fell short in other cases.
For example, take the following code
tsdeclare function f<T>(x?: T): T;let [x, y, z] = f();
绑定模式 [x, y, z] 提示 f 应该生成一个 [any, any, any] 元组;但 f 实际上不应该根据绑定模式更改其类型参数。它不能根据被赋值的对象突然生成一个新的类似数组的值,所以绑定模式类型对生成的类型影响太大。更重要的是,由于绑定模式类型充满了 any,我们最终得到 x、y 和 z 的类型是 any。
🌐 The binding pattern [x, y, z] hinted that f should produce an [any, any, any] tuple;
but f really shouldn’t change its type argument based on a binding pattern.
It can’t suddenly conjure up a new array-like value based on what it’s being assigned to, so the binding pattern type has way too much influence on the produced type.
On top of that, because the binding pattern type is full of anys, we’re left with x, y, and z being typed as any.
在 TypeScript 4.8 中,这些绑定模式从不被用作类型参数的候选项。相反,它们仅在参数需要更具体类型时才会被参考,就像我们在 chooseRandomly 示例中看到的那样。如果你需要恢复旧行为,你总是可以提供显式的类型参数。
🌐 In TypeScript 4.8, these binding patterns are never used as candidates for type arguments.
Instead, they’re just consulted in case a parameter needs a more specific type like in our chooseRandomly example.
If you need to revert to the old behavior, you can always provide explicit type arguments.
如果你想了解更多,可以在 GitHub 上查看更改。
🌐 You can look at the change on GitHub if you’re curious to learn more.
文件监控修复(特别是在 git checkout 之间)
🌐 File-Watching Fixes (Especially Across git checkouts)
我们长期存在一个问题,即 TypeScript 在 --watch 模式和编辑器场景下处理某些文件更改时非常困难。有时症状表现为过时或不准确的错误提示,需要重启 tsc 或 VS Code 才能解决。通常这些问题会在 Unix 系统上发生,你可能在使用 vim 保存文件或在 git 中切换分支后遇到过这些情况。
🌐 We’ve had a long-standing bug where TypeScript has a very hard time with certain file changes in --watch mode and editor scenarios.
Sometimes the symptoms are stale or inaccurate errors that might show up that require restarting tsc or VS Code.
Frequently these occur on Unix systems, and you might have seen these after saving a file with vim or swapping branches in git.
这是由于对 Node.js 如何处理跨文件系统的重命名事件的假设所导致的。Linux 和 macOS 使用的文件系统利用 inode,而 Node.js 会将文件监视器附加到 inode 而不是文件路径。因此,当 Node.js 返回 一个监视器对象 时,它可能正在监视一个路径或 inode,这取决于平台和文件系统。
🌐 This was caused by assumptions of how Node.js handles rename events across file systems. File systems used by Linux and macOS utilize inodes, and Node.js will attach file watchers to inodes rather than file paths. So when Node.js returns a watcher object, it might be watching a path or an inode depending on the platform and file system.
为了提高效率,TypeScript 会尝试重用相同的监视器对象,如果它检测到某个路径在磁盘上仍然存在。问题就出在这里,因为即使某个文件仍然存在于该路径上,也可能已经创建了一个不同的文件,而该文件会有不同的 inode。因此,TypeScript 最终可能会重用监视器对象,而不是在原始位置安装新的监视器,从而去监视可能完全不相关的文件的变化。现在,TypeScript 4.8 在 inode 系统上能够处理这些情况,并正确安装新的监视器,从而解决了此问题。
🌐 To be a bit more efficient, TypeScript tries to reuse the same watcher objects if it detects a path still exists on disk. This is where things went wrong, because even if a file still exists at that path, a distinct file might have been created, and that file will have a different inode. So TypeScript would end up reusing the watcher object instead of installing a new watcher at the original location, and watch for changes at what might be a totally irrelevant file. So TypeScript 4.8 now handles these cases on inode systems and properly installs a new watcher and fixes this.
我们想向 Marc Celani 及其在 Airtable 的团队表示感谢,他们投入大量时间调查所遇到的问题并指出了根本原因。你可以在这里查看 关于文件监控的具体修复。
🌐 We’d like to extend our thanks to Marc Celani and his team at Airtable who invested lots of time in investigating the issues they were experiencing and pointing out the root cause. You can view the specific fixes around file-watching here.
查找所有引用性能改进
🌐 Find-All-References Performance Improvements
在编辑器中运行“查找所有引用”时,TypeScript 现在能够更智能地汇总引用。 这将 TypeScript 在其自身代码库中搜索常用标识符所花费的时间减少了约 20%。
🌐 When running find-all-references in your editor, TypeScript is now able to act a little smarter as it aggregates references. This reduced the amount of time TypeScript took to search a widely-used identifier in its own codebase by about 20%.
从自动导入中排除特定文件
🌐 Exclude Specific Files from Auto-Imports
TypeScript 4.8 引入了一个编辑器偏好设置,用于将文件排除在自动导入之外。在 Visual Studio Code 中,可以在设置界面的“自动导入文件排除模式”下添加文件名或通配符,或在 .vscode/settings.json 文件中进行配置:
🌐 TypeScript 4.8 introduces an editor preference for excluding files from auto-imports.
In Visual Studio Code, file names or globs can be added under “Auto Import File Exclude Patterns” in the Settings UI, or in a .vscode/settings.json file:
jsonc{// Note that `javascript.preferences.autoImportFileExcludePatterns` can be specified for JavaScript too."typescript.preferences.autoImportFileExcludePatterns": ["**/node_modules/@types/node"]}
在某些情况下,如果你无法避免在编译中包含某些模块或库,但又很少需要从它们导入,这可能会很有用。这些模块可能有很多导出内容,会污染自动导入列表并使导航更困难,而这个选项在这种情况下可以提供帮助。
🌐 This can be useful in cases where you can’t avoid having certain modules or libraries in your compilation but you rarely want to import from them. These modules might have lots of exports that can pollute the auto-imports list and make it harder to navigate, and this option can help in those situations.
你可以在这里查看更多关于实现的具体信息。
🌐 You can see more specifics about the implementation here.
错误修复和重大变更
🌐 Correctness Fixes and Breaking Changes
由于类型系统更改的性质,可进行的不影响某些代码的更改非常少;然而,有一些更改更可能需要调整现有代码。
🌐 Due to the nature of type system changes, there are very few changes that can be made that don’t affect some code; however, there are a few changes that are more likely to require adapting existing code.
lib.d.ts 更新
🌐 lib.d.ts Updates
虽然 TypeScript 力求避免重大破坏,但即使内置库中的小变化也可能引发问题。我们不预计 DOM 和 lib.d.ts 更新会导致重大破坏,但有一个值得注意的变化是,Error 上的 cause 属性现在的类型是 unknown,而不是 Error。
🌐 While TypeScript strives to avoid major breaks, even small changes in the built-in libraries can cause issues.
We don’t expect major breaks as a result of DOM and lib.d.ts updates, but one notable change is that the cause property on Errors now has the type unknown instead of Error.
无约束的泛型不再可分配给 {}
🌐 Unconstrained Generics No Longer Assignable to {}
在 TypeScript 4.8 中,对于启用了 strictNullChecks 的项目,当在 null 或 undefined 不允许的地方使用未约束的类型参数时,TypeScript 现在会正确地发出错误。这将包括任何期望 {}、object 或所有属性都是可选的对象类型的类型。
🌐 In TypeScript 4.8, for projects with strictNullChecks enabled, TypeScript will now correctly issue an error when an unconstrained type parameter is used in a position where null or undefined are not legal values.
That will include any type that expects {}, object, or an object type with all-optional properties.
下面是一个简单的示例。
🌐 A simple example can be seen in the following.
ts// Accepts any non-null non-undefined valuefunction bar(value: {}) {Object.keys(value); // This call throws on null/undefined at runtime.}// Unconstrained type parameter T...function foo<T>(x: T) {bar(x); // Used to be allowed, now is an error in 4.8.// ~// error: Argument of type 'T' is not assignable to parameter of type '{}'.}foo(undefined);
如上所示,这段代码存在潜在的错误 —— 值 null 和 undefined 可能会通过这些未约束的类型参数间接传递到本不应接收这些值的代码中。
🌐 As demonstrated above, code like this has a potential bug - the values null and undefined can be indirectly passed through these unconstrained type parameters to code that is not supposed to observe those values.
这种行为在类型位置中也会可见。一个例子是:
🌐 This behavior will also be visible in type positions. One example would be:
tsinterface Foo<T> {x: Bar<T>;}interface Bar<T extends {}> { }
现有不处理 null 和 undefined 的代码可以通过传递适当的约束来修复。
🌐 Existing code that didn’t want to handle null and undefined can be fixed by propagating the appropriate constraints through.
diff- function foo<T>(x: T) {+ function foo<T extends {}>(x: T) {
另一种解决方法是在运行时检查 null 和 undefined。
🌐 Another work-around would be to check for null and undefined at runtime.
difffunction foo<T>(x: T) {+ if (x !== null && x !== undefined) {bar(x);+ }}
如果你知道出于某种原因,你的泛型值不能是 null 或 undefined,你可以直接使用非空断言。
🌐 And if you know that for some reason, your generic value can’t be null or undefined, you can just use a non-null assertion.
difffunction foo<T>(x: T) {- bar(x);+ bar(x!);}
在涉及类型时,你通常需要传播约束,或者将你的类型与 {} 进行交集。
🌐 When it comes to types, you’ll often either need to propagate constraints, or intersect your types with {}.
欲了解更多信息,你可以查看引入此更改的内容,以及有关无约束泛型现在如何工作的具体讨论问题。
🌐 For more information, you can see the change that introduced this along with the specific discussion issue regarding how unconstrained generics now work.
装饰器被放置在 TypeScript 语法树的 modifiers 上
🌐 Decorators are placed on modifiers on TypeScript’s Syntax Trees
TC39 当前对装饰器的方向意味着 TypeScript 必须处理装饰器位置上的变化。以前,TypeScript 假设装饰器总是放在所有关键字/修饰符之前。例如
🌐 The current direction of decorators in TC39 means that TypeScript will have to handle a break in terms of placement of decorators. Previously, TypeScript assumed decorators would always be placed prior to all keywords/modifiers. For example
ts@decoratorexport class Foo {// ...}
当前提出的装饰器不支持此语法。相反,export 关键字必须出现在装饰器之前。
🌐 Decorators as currently proposed do not support this syntax.
Instead, the export keyword must precede the decorator.
tsexport @decorator class Foo {// ...}
不幸的是,TypeScript 的语法树是具体的而不是抽象的,而我们的架构期望语法树节点的字段在彼此之前或之后完全有序。为了同时支持传统装饰器和提议中的装饰器,TypeScript 将必须优雅地解析并插入修饰符和装饰器。
🌐 Unfortunately, TypeScript’s trees are concrete rather than abstract, and our architecture expects syntax tree node fields to be entirely ordered before or after each other. To support both legacy decorators and decorators as proposed, TypeScript will have to gracefully parse, and intersperse, modifiers and decorators.
为此,它暴露了一个名为 ModifierLike 的新类型别名,该别名可以是 Modifier 或 Decorator。
🌐 To do this, it exposes a new type alias called ModifierLike which is a Modifier or a Decorator.
tsexport type ModifierLike = Modifier | Decorator;
装饰器现在放置在与 modifiers 相同的字段中,当设置时,modifiers 现在是 NodeArray<ModifierLike>,并且整个字段已被弃用。
🌐 Decorators are now placed in the same field as modifiers which is now a NodeArray<ModifierLike> when set, and the entire field is deprecated.
diff- readonly modifiers?: NodeArray<Modifier> | undefined;+ /**+ * @deprecated ...+ * Use `ts.canHaveModifiers()` to test whether a `Node` can have modifiers.+ * Use `ts.getModifiers()` to get the modifiers of a `Node`.+ * ...+ */+ readonly modifiers?: NodeArray<ModifierLike> | undefined;
所有现有的 decorators 属性已被标记为已弃用,如果读取,它们将始终是 undefined。
该类型也已更改为 undefined,以便现有工具能够正确处理它们。
🌐 All existing decorators properties have been marked as deprecated and will always be undefined if read.
The type has also been changed to undefined so that existing tools know to handle them correctly.
diff- readonly decorators?: NodeArray<Decorator> | undefined;+ /**+ * @deprecated ...+ * Use `ts.canHaveDecorators()` to test whether a `Node` can have decorators.+ * Use `ts.getDecorators()` to get the decorators of a `Node`.+ * ...+ */+ readonly decorators?: undefined;
为了避免新的弃用警告和其他问题,TypeScript 现在提供了四个新函数,以替代 decorators 和 modifiers 属性。
有用于测试节点是否具有支持修饰符和装饰器的单独谓词函数,以及用于获取它们的相应访问函数。
🌐 To avoid new deprecation warnings and other issues, TypeScript now exposes four new functions to use in place of the decorators and modifiers properties.
There are individual predicates for testing whether a node has support modifiers and decorators, along with respective accessor functions for grabbing them.
tsfunction canHaveModifiers(node: Node): node is HasModifiers;function getModifiers(node: HasModifiers): readonly Modifier[] | undefined;function canHaveDecorators(node: Node): node is HasDecorators;function getDecorators(node: HasDecorators): readonly Decorator[] | undefined;
作为如何从节点访问修饰符的示例,你可以这样写:
🌐 As an example of how to access modifiers off of a node, you can write
tsconst modifiers = canHaveModifiers(myNode) ? getModifiers(myNode) : undefined;
请注意,每次调用 getModifiers 和 getDecorators 可能会分配一个新的数组。
🌐 With the note that each call to getModifiers and getDecorators may allocate a new array.
更多信息,请参阅周围的变化
🌐 For more information, see changes around
JavaScript 文件中无法导入/导出类型
🌐 Types Cannot Be Imported/Exported in JavaScript Files
TypeScript 之前允许 JavaScript 文件在 import 和 export 语句中导入和导出仅声明了类型但没有值的实体。这种行为是错误的,因为对不存在的值进行命名导入和导出将会在 ECMAScript 模块下导致运行时错误。当 JavaScript 文件在 --checkJs 下或通过 // @ts-check 注释进行类型检查时,TypeScript 现在会发出错误。
🌐 TypeScript previously allowed JavaScript files to import and export entities declared with a type, but no value, in import and export statements.
This behavior was incorrect, because named imports and exports for values that don’t exist will cause a runtime error under ECMAScript modules.
When a JavaScript file is type-checked under --checkJs or through a // @ts-check comment, TypeScript will now issue an error.
ts// @ts-check// Will fail at runtime because 'SomeType' is not a value.import { someValue, SomeType } from "some-module";/*** @type {SomeType}*/export const myValue = someValue;/*** @typedef {string | number} MyType*/// Will fail at runtime because 'MyType' is not a value.export { MyType as MyExportedType };
要从其他模块引用类型,你可以直接限定导入。
🌐 To reference a type from another module, you can instead directly qualify the import.
diff- import { someValue, SomeType } from "some-module";+ import { someValue } from "some-module";/**- * @type {SomeType}+ * @type {import("some-module").SomeType}*/export const myValue = someValue;
要导出一个类型,你可以在 JSDoc 中使用 /** @typedef */ 注释。@typedef 注释会自动从其所在的模块导出类型。
🌐 To export a type, you can just use a /** @typedef */ comment in JSDoc.
@typedef comments already automatically export types from their containing modules.
diff/*** @typedef {string | number} MyType*/+ /**+ * @typedef {MyType} MyExportedType+ */- export { MyType as MyExportedType };
你可以在这里阅读有关此变更的更多信息。
🌐 You can read more about the change here.
绑定模式不直接影响推断候选对象
🌐 Binding Patterns Do Not Directly Contribute to Inference Candidates
如上所述,绑定模式不再改变函数调用中推断结果的类型。你可以在这里阅读更多关于原始更改的信息。
🌐 As mentioned above, binding patterns no longer change the type of inference results in function calls. You can read more about the original change here.
绑定模式中未使用的重命名现在在类型签名中被视为错误
🌐 Unused Renames in Binding Patterns are Now Errors in Type Signatures
TypeScript 的类型注解语法常常看起来可以在解构值时使用。例如,看看以下函数。
🌐 TypeScript’s type annotation syntax often looks like it can be used when destructuring values. For example, take the following function.
tsdeclare function makePerson({ name: string, age: number }): Person;
你可能看到这个签名会认为 makePerson 显然接收一个具有 name 属性(类型为 string)和 age 属性(类型为 number)的对象;然而,JavaScript 的解构语法实际上在这里占据了优先权。makePerson 确实表示它将接收一个具有 name 和 age 属性的对象,但它并没有为它们指定类型,而只是表示将 name 和 age 分别重命名为 string 和 number。
🌐 You might read this signature and think that makePerson obviously takes an object with a name property with the type string and an age property with the type number;
however, JavaScript’s destructuring syntax is actually taking precedence here.
makePerson does say that it’s going to take an object with a name and an age property, but instead of specifying a type for them, it’s just saying that it renames name and age to string and number respectively.
在纯类型构造中,编写这样的代码毫无用处,而且通常是一个错误,因为开发者通常会假设他们正在编写类型注释。
🌐 In a pure type construct, writing code like this is useless, and typically a mistake since developers usually assume they’re writing a type annotation.
除非在签名中稍后引用,否则 TypeScript 4.8 会将这些视为错误。上面签名的正确写法如下:
🌐 TypeScript 4.8 makes these an error unless they’re referenced later in the signature. The correct way to write the above signature would be as follows:
tsdeclare function makePerson(options: { name: string, age: number }): Person;// ordeclare function makePerson({ name, age }: { name: string, age: number }): Person;
此更改可以捕捉声明中的错误,并且有助于改进现有代码。我们要感谢 GitHub 用户 uhyo 提供的此检查。你可以在这里了解更多关于此更改的信息。
🌐 This change can catch bugs in declarations, and has been helpful for improving existing code. We’d like to extend our thanks to GitHub user uhyo for providing this check. You can read up on the change here.