导入属性
¥Import Attributes
TypeScript 5.3 支持 导入属性 提案的最新更新。
¥TypeScript 5.3 supports the latest updates to the import attributes proposal.
导入属性的一个用例是向运行时提供有关模块预期格式的信息。
¥One use-case of import attributes is to provide information about the expected format of a module to the runtime.
ts
// We only want this to be interpreted as JSON,// not a runnable/malicious JavaScript file with a `.json` extension.import obj from "./something.json" with { type: "json" };
由于这些属性是特定于宿主的,因此 TypeScript 不会检查它们的内容,因此它们只是被保留下来,以便浏览器和运行时能够处理它们(并且可能出现错误)。
¥The contents of these attributes are not checked by TypeScript since they’re host-specific, and are simply left alone so that browsers and runtimes can handle them (and possibly error).
ts
// TypeScript is fine with this.// But your browser? Probably not.import * as foo from "./foo.js" with { type: "fluffy bunny" };
动态 import()
调用还可以通过第二个参数使用导入属性。
¥Dynamic import()
calls can also use import attributes through a second argument.
ts
const obj = await import("./something.json", {with: { type: "json" }});
第二个参数的预期类型由名为 ImportCallOptions
的类型定义,默认情况下,该类型仅接受名为 with
的属性。
¥The expected type of that second argument is defined by a type called ImportCallOptions
, which by default just expects a property called with
.
请注意,导入属性是早期提案 “导入断言”,已在 TypeScript 4.5 中实现 的演变。最明显的区别是使用 with
关键字而不是 assert
关键字。但不太明显的区别是,运行时现在可以自由地使用属性来指导导入路径的解析和解释,而导入断言只能在加载模块后断言某些特性。
¥Note that import attributes are an evolution of an earlier proposal called “import assertions”, which were implemented in TypeScript 4.5.
The most obvious difference is the use of the with
keyword over the assert
keyword.
But the less-visible difference is that runtimes are now free to use attributes to guide the resolution and interpretation of import paths, whereas import assertions could only assert some characteristics after loading a module.
随着时间的推移,TypeScript 将弃用旧的导入断言语法,转而采用建议的导入属性语法。现有的使用 assert
的代码应该迁移到 with
关键字。需要导入属性的新代码应该只使用 with
。
¥Over time, TypeScript will be deprecating the old syntax for import assertions in favor of the proposed syntax for import attributes.
Existing code using assert
should migrate towards the with
keyword.
New code that needs an import attribute should use with
exclusively.
我们要感谢 Oleksandr Tarasiuk 和 实现此提案!我们也想引用 Wenlu Wang 对 导入断言 的实现!
¥We’d like to thank Oleksandr Tarasiuk for implementing this proposal! And we’d also like to call out Wenlu Wang for their implementation of import assertions!
导入类型中稳定支持 resolution-mode
¥Stable Support resolution-mode
in Import Types
在 TypeScript 4.7 中,TypeScript 在 /// <reference types="..." />
中添加了对 resolution-mode
属性的支持,以控制说明符是否应通过 import
或 require
语义进行解析。
¥In TypeScript 4.7, TypeScript added support for a resolution-mode
attribute in /// <reference types="..." />
to control whether a specifier should be resolved via import
or require
semantics.
ts
/// <reference types="pkg" resolution-mode="require" />// or/// <reference types="pkg" resolution-mode="import" />
在仅类型导入的导入断言中添加了相应的字段;但是,它仅在 TypeScript 的 Nightly 版本中受支持。理由是,从本质上讲,导入断言并非旨在指导模块解析。所以此功能仅在夜间模式下实验性地发布,以获得更多反馈。
¥A corresponding field was added to import assertions on type-only imports as well; however, it was only supported in nightly versions of TypeScript. The rationale was that in spirit, import assertions were not intended to guide module resolution. So this feature was shipped experimentally in a nightly-only mode to get more feedback.
但鉴于 导入属性 可以引导解析,并且我们已经看到了合理的用例,TypeScript 5.3 现在支持 import type
的 resolution-mode
属性。
¥But given that import attributes can guide resolution, and that we’ve seen reasonable use-cases, TypeScript 5.3 now supports the resolution-mode
attribute for import type
.
ts
// Resolve `pkg` as if we were importing with a `require()`import type { TypeFromRequire } from "pkg" with {"resolution-mode": "require"};// Resolve `pkg` as if we were importing with an `import`import type { TypeFromImport } from "pkg" with {"resolution-mode": "import"};export interface MergedType extends TypeFromRequire, TypeFromImport {}
这些导入属性也可用于 import()
类型。
¥These import attributes can also be used on import()
types.
ts
export type TypeFromRequire =import("pkg", { with: { "resolution-mode": "require" } }).TypeFromRequire;export type TypeFromImport =import("pkg", { with: { "resolution-mode": "import" } }).TypeFromImport;export interface MergedType extends TypeFromRequire, TypeFromImport {}
详情请见 查看此处的变更。
¥For more information, check out the change here
resolution-mode
所有模块模式均支持
¥resolution-mode
Supported in All Module Modes
以前,仅允许在 moduleResolution
选项 node16
和 nodenext
下使用 resolution-mode
。为了更轻松地查找专门用于类型目的的模块,resolution-mode
现在可以在其他所有 moduleResolution
选项(例如 bundler
、node10
)中正常工作,并且在 classic
下不会出错。
¥Previously, using resolution-mode
was only allowed under the moduleResolution
options node16
and nodenext
.
To make it easier to look up modules specifically for type purposes, resolution-mode
now works appropriately in all other moduleResolution
options like bundler
, node10
, and simply doesn’t error under classic
.
更多信息请见 查看实现拉取请求!
¥For more information, see the implementing pull request.
switch (true)
类型缩小
¥switch (true)
Narrowing
TypeScript 5.3 现在可以根据 switch (true)
中每个 case
子句中的条件执行范围缩小。
¥TypeScript 5.3 now can perform narrowing based on conditions in each case
clause within a switch (true)
.
ts
function f(x: unknown) {switch (true) {case typeof x === "string":// 'x' is a 'string' hereconsole.log(x.toUpperCase());// falls through...case Array.isArray(x):// 'x' is a 'string | any[]' here.console.log(x.length);// falls through...default:// 'x' is 'unknown' here.// ...}}
此功能 由 Mateusz Burzyński 牵头开发 初步工作。我们想为此贡献授予 “谢谢!” 奖项。
¥This feature was spearheaded initial work by Mateusz Burzyński We’d like to extend a “thank you!” for this contribution.
通过布尔值比较缩小范围
¥Narrowing On Comparisons to Booleans
有时,你可能会发现自己在某个条件中直接与 true
或 false
进行比较。通常这些比较是不必要的,但你可能更喜欢将其作为样式问题,或避免与 JavaScript 真值相关的某些问题。无论如何,以前的 TypeScript 在执行范围缩小时无法识别此类形式。
¥Occasionally you may find yourself performing a direct comparison with true
or false
in a condition.
Usually these are unnecessary comparisons, but you might prefer it as a point of style, or to avoid certain issues around JavaScript truthiness.
Regardless, previously TypeScript just didn’t recognize such forms when performing narrowing.
TypeScript 5.3 现在在范围缩小变量时能够跟上并理解这些表达式。
¥TypeScript 5.3 now keeps up and understands these expressions when narrowing variables.
ts
interface A {a: string;}interface B {b: string;}type MyType = A | B;function isA(x: MyType): x is A {return "a" in x;}function someFn(x: MyType) {if (isA(x) === true) {console.log(x.a); // works!}}
我们要感谢 Mateusz Burzyński 和 拉取请求 实现了这个功能。
¥We’d like to thank Mateusz Burzyński for the pull request that implemented this.
instanceof
通过 Symbol.hasInstance
缩小范围
¥instanceof
Narrowing Through Symbol.hasInstance
JavaScript 中一个略显深奥的特性是可以覆盖 instanceof
运算符的行为。为此,instanceof
运算符右侧的值需要具有一个以 Symbol.hasInstance
命名的特定方法。
¥A slightly esoteric feature of JavaScript is that it is possible to override the behavior of the instanceof
operator.
To do so, the value on the right side of the instanceof
operator needs to have a specific method named by Symbol.hasInstance
.
js
class Weirdo {static [Symbol.hasInstance](testedValue) {// wait, what?return testedValue === undefined;}}// falseconsole.log(new Thing() instanceof Weirdo);// trueconsole.log(undefined instanceof Weirdo);
为了在 instanceof
中更好地模拟这种行为,TypeScript 现在会检查是否存在这样的 [Symbol.hasInstance]
方法并将其声明为类型谓词函数。如果是这样,instanceof
运算符左侧的测试值将通过该类型谓词进行适当的缩小。
¥To better model this behavior in instanceof
, TypeScript now checks if such a [Symbol.hasInstance]
method exists and is declared as a type predicate function.
If it does, the tested value on the left side of the instanceof
operator will be narrowed appropriately by that type predicate.
ts
interface PointLike {x: number;y: number;}class Point implements PointLike {x: number;y: number;constructor(x: number, y: number) {this.x = x;this.y = y;}distanceFromOrigin() {return Math.sqrt(this.x ** 2 + this.y ** 2);}static [Symbol.hasInstance](val: unknown): val is PointLike {return !!val && typeof val === "object" &&"x" in val && "y" in val &&typeof val.x === "number" &&typeof val.y === "number";}}function f(value: unknown) {if (value instanceof Point) {// Can access both of these - correct!value.x;value.y;// Can't access this - we have a 'PointLike',// but we don't *actually* have a 'Point'.value.distanceFromOrigin();}}
正如你在本例中看到的,Point
定义了它自己的 [Symbol.hasInstance]
方法。它实际上充当了名为 PointLike
的单独类型的自定义类型保护。在函数 f
中,我们能够使用 instanceof
将 value
的范围缩小到 PointLike
,但无法缩小到 Point
。这意味着我们可以访问属性 x
和 y
,但不能访问方法 distanceFromOrigin
。
¥As you can see in this example, Point
defines its own [Symbol.hasInstance]
method.
It actually acts as a custom type guard over a separate type called PointLike
.
In the function f
, we were able to narrow value
down to a PointLike
with instanceof
, but not a Point
.
That means that we can access the properties x
and y
, but not the method distanceFromOrigin
.
更多信息,你可以查看 在此处阅读此变更信息。
¥For more information, you can read up on this change here.
检查实例字段的 super
属性访问
¥Checks for super
Property Accesses on Instance Fields
在 JavaScript 中,可以通过 super
关键字访问基类中的声明。
¥In JavaScript, it’s possible to access a declaration in a base class through the super
keyword.
js
class Base {someMethod() {console.log("Base method called!");}}class Derived extends Base {someMethod() {console.log("Derived method called!");super.someMethod();}}new Derived().someMethod();// Prints:// Derived method called!// Base method called!
这与编写类似 this.someMethod()
的代码不同,因为那样可能会调用重写的方法。这是一个微妙的区别,如果一个声明从未被覆盖,那么两者通常可以互换,这使得区别更加微妙。
¥This is different from writing something like this.someMethod()
, since that could invoke an overridden method.
This is a subtle distinction, made more subtle by the fact that often the two can be interchangeable if a declaration is never overridden at all.
js
class Base {someMethod() {console.log("someMethod called!");}}class Derived extends Base {someOtherMethod() {// These act identically.this.someMethod();super.someMethod();}}new Derived().someOtherMethod();// Prints:// someMethod called!// someMethod called!
问题是 super
只适用于原型上声明的成员,而不是实例属性。这意味着如果你编写了 super.someMethod()
,但 someMethod
被定义为字段,你将收到运行时错误!
¥The problem is using them interchangeably is that super
only works on members declared on the prototype — not instance properties.
That means that if you wrote super.someMethod()
, but someMethod
was defined as a field, you’d get a runtime error!
ts
class Base {someMethod = () => {console.log("someMethod called!");}}class Derived extends Base {someOtherMethod() {super.someMethod();}}new Derived().someOtherMethod();// 💥// Doesn't work because 'super.someMethod' is 'undefined'.
TypeScript 5.3 现在会更仔细地检查 super
属性访问/方法调用,以查看它们是否对应于类字段。如果确实如此,我们将收到类型检查错误。
¥TypeScript 5.3 now more-closely inspects super
property accesses/method calls to see if they correspond to class fields.
If they do, we’ll now get a type-checking error.
此检查 的贡献要感谢 Jack Works!
¥This check was contributed thanks to Jack Works!
类型的交互式嵌入提示
¥Interactive Inlay Hints for Types
TypeScript 的嵌入提示现在支持跳转到类型的定义!这使得随意导航代码变得更加容易。
¥TypeScript’s inlay hints now support jumping to the definition of types! This makes it easier to casually navigate your code.
查看更多 实现(此处)。
¥See more at the implementation here.
设置优先使用 type
自动导入
¥Settings to Prefer type
Auto-Imports
以前,当 TypeScript 为类型位置中的某些内容生成自动导入时,它会根据你的设置添加 type
修饰符。例如,在以下情况下获取 Person
的自动导入:
¥Previously when TypeScript generated auto-imports for something in a type position, it would add a type
modifier based on your settings.
For example, when getting an auto-import on Person
in the following:
ts
export let p: Person
TypeScript 的编辑体验通常会为 Person
添加导入,如下所示:
¥TypeScript’s editing experience would usually add an import for Person
as:
ts
import { Person } from "./types";export let p: Person
在某些设置(例如 verbatimModuleSyntax
)下,它会添加 type
修饰符:
¥and under certain settings like verbatimModuleSyntax
, it would add the type
modifier:
ts
import { type Person } from "./types";export let p: Person
但是,你的代码库可能无法使用其中一些选项;或者你只是倾向于在可能的情况下显式导入 type
。
¥However, maybe your codebase isn’t able to use some of these options; or you just have a preference for explicit type
imports when possible.
最近的变化,TypeScript 现在允许将其作为特定于编辑器的选项。在 Visual Studio Code 中,你可以在 UI 中的“TypeScript › 首选项”下启用它:仅优先自动导入类型”,或作为 JSON 配置选项 typescript.preferences.preferTypeOnlyAutoImports
¥With a recent change, TypeScript now enables this to be an editor-specific option.
In Visual Studio Code, you can enable it in the UI under “TypeScript › Preferences: Prefer Type Only Auto Imports”, or as the JSON configuration option typescript.preferences.preferTypeOnlyAutoImports
通过跳过 JSDoc 解析进行优化
¥Optimizations by Skipping JSDoc Parsing
通过 tsc
运行 TypeScript 时,编译器现在将避免解析 JSDoc。这本身就减少了解析时间,但也减少了存储注释的内存使用量以及垃圾收集的时间。总而言之,在 --watch
模式下,你应该会看到编译速度略快一些,反馈也更快一些。
¥When running TypeScript via tsc
, the compiler will now avoid parsing JSDoc.
This drops parsing time on its own, but also reduces memory usage to store comments along with time spent in garbage collection.
All-in-all, you should see slightly faster compiles and quicker feedback in --watch
mode.
¥The specific changes can be viewed here.
由于并非所有使用 TypeScript 的工具都需要存储 JSDoc(例如 typescript-eslint 和 Prettier),因此这种解析策略已作为 API 本身的一部分浮出水面。这可以使这些工具获得我们为 TypeScript 编译器带来的相同的内存和速度改进。注释解析策略的新选项在 JSDocParsingMode
中进行了描述。更多信息请参见 关于此拉取请求。
¥Because not every tool using TypeScript will need to store JSDoc (e.g. typescript-eslint and Prettier), this parsing strategy has been surfaced as part of the API itself.
This can enable these tools to gain the same memory and speed improvements we’ve brought to the TypeScript compiler.
The new options for comment parsing strategy are described in JSDocParsingMode
.
More information is available on this pull request.
通过比较非规范化交叉进行优化
¥Optimizations by Comparing Non-Normalized Intersections
在 TypeScript 中,并集和交叉始终遵循特定形式,其中交叉不能包含联合类型。这意味着当我们在像 A & (B | C)
这样的联合体上创建交叉时,该交叉将被规范化为 (A & B) | (A & C)
。不过,在某些情况下,类型系统会为了显示目的而保持原始形式。
¥In TypeScript, unions and intersections always follow a specific form, where intersections can’t contain union types.
That means that when we create an intersection over a union like A & (B | C)
, that intersection will be normalized into (A & B) | (A & C)
.
Still, in some cases the type system will maintain the original form for display purposes.
事实证明,原始形式可用于一些巧妙的类型间快速路径比较。
¥It turns out that the original form can be used for some clever fast-path comparisons between types.
例如,假设我们有 SomeType & (Type1 | Type2 | ... | Type99999NINE)
函数,我们想看看它是否可以赋值给 SomeType
。回想一下,我们的源类型实际上没有交叉 - 我们有一个看起来像 (SomeType & Type1) | (SomeType & Type2) | ... |(SomeType & Type99999NINE)
的并集。检查联合是否可以分配给某个目标类型时,我们必须检查联合的每个成员是否都可以分配给目标类型,这可能会非常慢。
¥For example, let’s say we have SomeType & (Type1 | Type2 | ... | Type99999NINE)
and we want to see if that’s assignable to SomeType
.
Recall that we don’t really have an intersection as our source type — we have a union that looks like (SomeType & Type1) | (SomeType & Type2) | ... |(SomeType & Type99999NINE)
.
When checking if a union is assignable to some target type, we have to check if every member of the union is assignable to the target type, and that can be very slow.
在 TypeScript 5.3 中,我们可以窥见之前隐藏的原始交叉形式。比较类型时,我们会快速检查目标是否存在于源交叉的任何组成部分中。
¥In TypeScript 5.3, we peek at the original intersection form that we were able to tuck away. When we compare the types, we do a quick check to see if the target exists in any constituent of the source intersection.
更多信息请见 查看此拉取请求!
¥For more information, see this pull request.
tsserverlibrary.js
和 typescript.js
之间的合并
¥Consolidation Between tsserverlibrary.js
and typescript.js
TypeScript 本身附带两个库文件:tsserverlibrary.js
和 typescript.js
。某些 API 仅在 tsserverlibrary.js
中可用(例如 ProjectService
API),这些 API 可能对某些导入器有用。尽管如此,这两个包仍然是不同的,包中有很多重叠和重复的代码。此外,由于自动导入或肌肉记忆,始终使用其中一个可能具有挑战性。意外加载两个模块太容易了,代码可能无法在不同的 API 实例上正常工作。即使它确实有效,加载第二个 bundle 也会增加资源使用量。
¥TypeScript itself ships two library files: tsserverlibrary.js
and typescript.js
.
There are certain APIs available only in tsserverlibrary.js
(like the ProjectService
API), which may be useful to some importers.
Still, the two are distinct bundles with a lot of overlap, duplicating code in the package.
What’s more, it can be challenging to consistently use one over the other due to auto-imports or muscle memory.
Accidentally loading both modules is far too easy, and code may not work properly on a different instance of the API.
Even if it does work, loading a second bundle increases resource usage.
鉴于此,我们决定合并两者。typescript.js
现在包含 tsserverlibrary.js
过去包含的内容,而 tsserverlibrary.js
现在只是重新导出 typescript.js
。比较合并前后,我们发现包大小有所减少:
¥Given this, we’ve decided to consolidate the two.
typescript.js
now contains what tsserverlibrary.js
used to contain, and tsserverlibrary.js
now simply re-exports typescript.js
.
Comparing the before/after of this consolidation, we saw the following reduction in package size:
之前 | 之后 | 差异 | 差异(百分比) | |
---|---|---|---|---|
已打包 | 6.90 MiB | 5.48 MiB | -1.42 MiB | -20.61% |
解压后 | 38.74 MiB | 30.41 MiB | -8.33 MiB | -21.50% |
之前 | 之后 | 差异 | 差异(百分比) | |
---|---|---|---|---|
lib/tsserverlibrary.d.ts |
570.95 KiB | 865.00 B | -570.10 KiB | -99.85% |
lib/tsserverlibrary.js |
8.57 MiB | 1012.00 B | -8.57 MiB | -99.99% |
lib/typescript.d.ts |
396.27 KiB | 570.95 KiB | +174.68 KiB | +44.08% |
lib/typescript.js |
7.95 MiB | 8.57 MiB | +637.53 KiB | +7.84% |
换句话说,这将包大小减少 20.5% 以上。
¥In other words, this is over a 20.5% reduction in package size.
更多信息,你可以查看 查看此处涉及的工作。
¥For more information, you can see the work involved here.
重大变更和正确性改进
¥Breaking Changes and Correctness Improvements
lib.d.ts
变更
¥lib.d.ts
Changes
为 DOM 生成的类型可能会对你的代码库产生影响。更多信息请见 查看 TypeScript 5.3 的 DOM 更新!
¥Types generated for the DOM may have an impact on your codebase. For more information, see the DOM updates for TypeScript 5.3.
检查实例属性的 super
访问
¥Checks for super
Accesses on Instance Properties
TypeScript 5.3 现在会检测 super.
属性访问所引用的声明是否为类字段,并触发错误。这可以防止运行时可能发生的错误。
¥TypeScript 5.3 now detects when the declaration referenced by a super.
property access is a class field and issues an error.
This prevents errors that might occur at runtime.