改进了交叉减少、联合兼容性和范围缩小
¥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
.
ts
function 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>
,而之前并非如此。
¥This is an improvement because intersection types like this can be reduced and assigned to, while conditional types currently cannot.
So NonNullable<NonNullable<T>>
now simplifies at least to NonNullable<T>
, whereas it didn’t before.
ts
function 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.
ts
function 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.
ts
function 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 特定语法即可运行。
¥value
now gets narrowed to T & {}
, and is now identical with NonNullable<T>
- so the body of the function just works with no TypeScript-specific syntax.
这些变化本身可能看起来很小。 - 但这是对多年来报告的大量文档缺陷的修复。
¥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 检查从字符串到原始类型再返回的路径是否匹配。如果它没有发现字符串可以是 “round-tripped”,那么它将回退到基本原始类型。
¥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
的项目引用构建。例如,TypeScript 现在能够避免在 --watch
模式下无操作更改期间花费时间更新时间戳,这使得重建速度更快,并避免与可能正在监视 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
在许多语言中,像 ==
这样的运算符对对象执行所谓的 “value” 相等性操作。例如,在 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 ==
.
py
if people_at_home == []:print("here's where I lie, broken inside. </3")adopt_animals()
JavaScript 中并非如此,对象(以及数组)之间的 ==
和 ===
检查两个引用是否指向同一个值。我们认为,JavaScript 中类似的代码充其量只是 JavaScript 开发者的早期试探,最坏的情况是生产代码中的 bug。这就是 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.
ts
if (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.
ts
declare 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
的低优先级候选类型,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
ts
declare 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 any
s, 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 checkout
s)
我们遇到了一个长期存在的错误,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 使用的文件系统使用 inodes 和 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
在编辑器中运行 find-all-references 时,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%.
¥You can read up more on the improvement here.
从自动导入中排除特定文件
¥Exclude Specific Files from Auto-Imports
TypeScript 4.8 引入了一个编辑器首选项,用于从自动导入中排除文件。在 Visual Studio Code 中,可以在“设置”UI 中的 “自动导入文件排除模式” 下或 .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 Error
s 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:
ts
interface 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.
diff
function 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.
diff
function 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.
ts
export @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
.
ts
export 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.
ts
function 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
ts
const 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 模块下导致运行时错误。当在 --checkJs
下或通过 // @ts-check
注释对 JavaScript 文件进行类型检查时,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.
ts
declare function makePerson({ name: string, age: number }): Person;
你可能会读到这个签名,并认为 makePerson
显然接受一个对象,该对象具有类型为 string
的 name
属性和类型为 number
的 age
属性;但是,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:
ts
declare 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.