译者 | 禾木木 责编 | 夏萌
出品 | CSDN(ID:CSDNnews)
近日,微软宣布正式发布 TypeScript 5.1版本。
如果你还不熟悉 TypeScript,它是一种建立在 JavaScript 基础上的语言,通过添加静态类型语法来构建的语言。这些类型可以描述我们程序的一些细节,可以使用 TypeScript 对代码进行类型检查,并在运行代码之前告诉你代码错误的相关信息。
此外,你还可以使用 TypeScript 编译器从代码中剥离类型,并为你提供可在任何地方运行的简洁易读的 JavaScript 代码。除了类型检查之外,TypeScript 还使用静态类型来支持强大的编辑器工具,例如自动完成、代码导航、重构等。
如果你在 Visual Studio Code 或 Visual Studio 这样的编辑器中使用过 JavaScript,那么你已经用上了类型和 TypeScript 带来的体验。
如果你已经在项目中使用 TypeScript,则可以通过 NuGet 获取它,也可以通过以下命令使用 npm 获取:
npm install -D typescript
以下是 TypeScript 5.1 版本中新增的主要功能:
更容易实现 undefined 函数的返回值类型
getter 和 setter 支持设置不同类型
JSX 元素和 JSX 标签类型之间解耦类型检查
带命名空间的 JSX 标签
typeRoots在模块解析中被查询
JSX 标签支持链接光标
@Param JSDoc 标签中支持代码补全
优化
重大变更
Beta版本和RC版有哪些新变化?
Beta版以后,纠正了装饰器中init钩子的一些行为,并作出调整。还对 isolatedModules 下的 emit 行为进行了修改,以确保脚本文件不会被重写为模块。这也意味着使用 transpileModule API 不会将脚本文件被解释为模块,因为它假定使用了 isolatedModules。
RC版以后,对内置重构进行了轻微的迭代。但,实现仍然需要一些改进。因此,你现在可能无法在大多数编辑器中访问它,只能通过使用 TypeScript 的夜间版本来选择。预计 TypeScript 5.2 或 TypeScript 5.1 的未来补丁版本将重新引入这种重构。
更容易实现 undefined 函数的返回值类型
在 JavaScript 中,如果函数在执行过程中没有返回值,就会返回 undefined:
function foo() {// no return
}// x = undefined
let x = foo();
然而,在以前版本的 TypeScript 中,只有返回值类型为 void 和 any 的函数可以没有 return语句。这意味着即使明确知道这个函数是返回 undefined 的,也需要至少有一个 return 语句。
// fine - we inferred that 'f1' returns 'void'
function f1() {// no returns
}// fine - 'void' doesn't need a return statement
function f2(): void {// no returns
}// fine - 'any' doesn't need a return statement
function f3(): any {// no returns
}// ❌ error!
// A function whose declared type is neither 'void' nor 'any' must return a value.
function f4(): undefined {// no returns
}
如果某个 API 希望函数返回 undefined。在以前的版本中,可能需要显式返回一个 undefined 或者显式添加一个 return 语句。
declare function takesFunction(f: () => undefined): undefined;// error!
// Argument of type '() => void' is not assignable to parameter of type '() => undefined'.
takesFunction(() => {// no returns
});// error!
// A function whose declared type is neither 'void' nor 'any' must return a value.
takesFunction((): undefined => {// no returns
});// error!
// Argument of type '() => void' is not assignable to parameter of type '() => undefined'.
takesFunction(() => {return;
});// works
takesFunction(() => {return undefined;
});// works
takesFunction((): undefined => {return;
});
这种行为令人困惑,尤其是在调用无法控制的函数时。理解推断 void 和 undefined 之间的相互作用,以及一个返回 undefined 的函数是否需要一个 return 语句等。
在 TypeScript 5.1 中,允许返回 undefined 的函数没有 return 语句。如下所示:
// worksfunction f4(): undefined { // no returns}// workstakesFunction((): undefined => { // no returns});
如果函数没有 return 并且被传递给期望返回 undefined 的函数参数,TypeScript 会推断该函数的返回类型为 undefined。
// Works in TypeScript 5.1!takesFunction(function f() { // ^ return type is undefined // no returns});// Works in TypeScript 5.1!takesFunction(function f() { // ^ return type is undefined return;});
为了解决另一个类似的痛点,在 TypeScript 的 --noImplicitReturns 选项下,仅返回 undefined 的函数现在具有与 void 类似的异常,因为并非每个代码路径都必须以显式 return 结束。
// Works in TypeScript 5.1 under '--noImplicitReturns'!
function f(): undefined {if (Math.random()) {// do some stuff...return;}
}
getter和setter支持设置不同类型
TypeScript 4.3 使得 get 和 set 访问器对可以指定两种不同的类型成为可能。
interface Serializer {set value(v: string | number | boolean);get value(): string;
}declare let box: Serializer;// Allows writing a 'boolean'
box.value = true;// Comes out as a 'string'
console.log(box.value.toUpperCase());
最初要求 get 类型必须是 set 类型的子类型。意味着以下的代码是合理的:
box.value = box.value;
但是,有许多现有的和提议的 API 在它们的 getter 和 setter 之间具有完全不相关的类型。
TypeScript 5.1 现在允许 get 和 set 访问器属性设置不同的类型,前提是它们具有显式类型注释。虽然此版本的 TypeScript 尚未更改这些内置接口的类型,但现在可以通过以下方式定义:
interface CSSStyleRule {get style(): CSSStyleDeclaration;set style(newValue: string);
}
也允许其他模式的使用,但如果某些基础状态尚未初始化,则访问器可以返回。
class SafeBox {#value: string | undefined;// 只接受字符串set value(newValue: string) {}// 必须检查 undefinedget value(): string | undefined {return this.#value;}
}
实际上,这与在 --exactOptionalProperties 下检查可选属性的方式类似。
JSX 元素和 JSX 标签类型之间解耦类型检查
TypeScript 在处理 JSX 时的一个痛点是对每个 JSX 元素标签类型的要求。而这个版本使得 JSX 库能够更准确地描述 JSX 组件的返回类型。对于很多人来说,这具体意味着可以在 React 中使用异步服务器组件。
例如,有以下 JSX 元素:
// A self-closing JSX tag
<Foo />// A regular element with an opening/closing tag
<Bar></Bar>
当对 <Foo />或<Bar></Bar> 进行类型检查时,TypeScript 总是会查找名为 JSX 的命名空间,并从中获取一个名为 Element 的类型,也就是在查找 JSX.Element。
但是,为了检查 Foo 或 Bar 本身是否是有效的标签名称,TypeScript 会粗略地获取由 Foo 或 Bar 返回或构造的类型,并检查与 JSX.Element 的兼容性(或者如果类型是可构造的,则检查另一种称为 JSX.ElementClass 的类型)。
这个限制意味着如果组件返回比 JSX.Element 更广泛的类型,则无法使用组件。
举一个具体的例子,React 的未来版本提议对返回 Promise 的组件提供有限支持,但是现有版本的TypeScript 无法表达这种类型,除非彻底放宽 JSX.Element 的类型限制。
import * as React from "react";async function Foo() {return <div></div>;
}
let element = <Foo />;
// ~~~
// 'Foo' cannot be used as a JSX component.
// Its return type 'Promise<Element>' is not a valid JSX element.
为了向库提供一种表达方式,TypeScript 5.1 现在会查找名为 JSX.ElementType 的类型。ElementType 精确指定什么可以有效用作 JSX 元素中的标签。因此,它可能会被定义为类似于以下这样:
namespace JSX {export type ElementType =// All the valid lowercase tagskeyof IntrinsicAttributes// Function components(props: any) => Element// Class componentsnew (props: any) => ElementClass;export interface IntrinsictAttributes extends /*...*/ {}export type Element = /*...*/;export type ClassElement = /*...*/;
}
命名空间的 JSX 标签
在使用 JSX 时,可以使用带命名空间的属性名。
import * as React from "react";// Both of these are equivalent:
const x = <Foo a:b="hello" />;
const y = <Foo a : b="hello" />;interface FooProps {"a:b": string;
}function Foo(props: FooProps) {return <div>{props["a:b"]}</div>;
}
当名称的第一部分是小写字母时,JSX.IntrinsicAttributes 上的命名空间属性名会以类似的方式进行查找。
// In some library's code or in an augmentation of that library:
namespace JSX {interface IntrinsicElements {["a:b"]: { prop: string };}
}// In our code:
let x = <a:b prop="hello!" />;
typeRoots 在模块解析中被查询
当 TypeScript 指定的模块查找策略无法解析路径时,它现在将解析相对于指定 typeRoots 的包。
引入 JSX 标签中链接光标
TypeScript 现在支持 JSX 标签名称的链接编辑。允许编辑器自动同时编辑多个位置。
这项新功能应该适用于 TypeScript 和 JavaScript 文件,并且可以在 Visual Studio Code Insiders 中启用。在 Visual Studio Code 中,勾选 Editor: Linked Editing 选项即可启用:
或者在 JSON 设置文件中配置 editor.linkedEditing:
{ // ... "editor.linkedEditing": true,}
Visual Studio 17.7 Preview 1 也将支持此功能。
@Param JSDoc 标签中支持代码补全
在 TypeScript 和 JavaScript 文件中输入 @param 标签时,TypeScript 现在提供代码补全。
优化
避免不必要的类型实例化
TypeScript 5.1 现在避免在已知不包含对外部类型参数的引用的对象类型中执行类型实例化。这有可能减少许多不必要的计算,并将 material-ui 的文档目录的类型检查时间减少 50% 以上。
通过减少不必要的类型实例化,TypeScript 5.1 在类型检查方面提供了更高的效率。尤其是在具有复杂类型层级或大型代码库的情况下,可以显著提高类型检查的性能。
联合字面量类型检查优化
在检查源类型是否属于联合类型时,TypeScript首先将使用该源类型的内部类型标识符进行快速查找。如果查找失败,则TypeScript会针对联合类型中的每种类型检查其兼容性。当将字面类型与仅包含字面类型的联合类型相关联时,TypeScript现在可以避免针对联合类型中的每种其他类型进行完整遍历。
这种优化可以将此问题中的代码的类型检查时间从约 45 秒减少到约 0.4 秒。
在解析JSDoc时,减少对扫描器的调用
在旧版本的 TypeScript 中,解析 JSDoc 注释时会使用扫描器/标记器将注释分解为细粒度的标记,并将内容重新组合。这在规范化注释文本方面可能是有帮助的,例如多个空格会被合并为一个空格,但这种方法会导致解析器和扫描器之间频繁的跳转,增加了 JSDoc 解析的开销。
TypeScript 5.1 对将 JSDoc 注释分解为扫描器/分词器中的内容更改了逻辑。扫描器现在将更大的内容块直接返回给解析器,以便根据需要进行处理。
这些更改已将几个 10Mb 的主要是注释的 JavaScript 文件的解析时间缩短了大约一半。
重大变更
ES2020 和 Node.js 14.17 作为最低运行时要求
TypeScript 5.1 现在包含了在 ECMAScript 2020 中引入的 JavaScript 功能。因此,TypeScript 至少需要在一个相对较新的运行时环境下运行。对于大多数用户来说,这意味着 TypeScript 现在只能在 Node.js 14.17 及更高版本上运行。
如果你尝试在较旧版本的 Node.js(如 Node 10 或 12)上运行 TypeScript 5.1,可能会遇到以下错误,无论是运行 tsc.js 还是 tsserver.js:
node_modules/typescript/lib/tsserver.js:2406 for (let i = startIndex ?? 0; i < array.length; i++) { ^SyntaxError: Unexpected token '?' at wrapSafe (internal/modules/cjs/loader.js:915:16) at Module._compile (internal/modules/cjs/loader.js:963:27) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10) at Module.load (internal/modules/cjs/loader.js:863:32) at Function.Module._load (internal/modules/cjs/loader.js:708:14) at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12) at internal/main/run_main_module.js:17:47
此外,如果你尝试安装 TypeScript,你将会收到类似以下的错误信息:
npm WARN EBADENGINE Unsupported engine {npm WARN EBADENGINE package: 'typescript@5.1.3',npm WARN EBADENGINE required: { node: '>=14.17' },npm WARN EBADENGINE current: { node: 'v12.22.12', npm: '8.19.2' }npm WARN EBADENGINE }
来自 Yarn:
error typescript@5.1.3: The engine "node" is incompatible with this module. Expected version ">=14.17". Got "12.22.12"error Found incompatible module.
显式指定了 typeRoots 后,将禁用对 node_modules/@types 的向上查找
以前,在 tsconfig.json 中指定了 typeRoots 选项但无法解析到任何 typeRoots 目录时,TypeScript 仍然会继续向上遍历父目录,尝试在每个父目录的 node_modules/@types 文件夹中解析包。
这种行为可能导致过多的查找,并且在 TypeScript 5.1 中已被禁用。因此,您可能会开始看到类似以下错误的错误,这是基于您的 tsconfig.json 中的 types 选项或 /// 指令的条目而产生的:
error TS2688: Cannot find type definition file for 'node'.error TS2688: Cannot find type definition file for 'mocha'.error TS2688: Cannot find type definition file for 'jasmine'.error TS2688: Cannot find type definition file for 'chai-http'.error TS2688: Cannot find type definition file for 'webpack-env"'.
解决方案通常是在您的 typeRoots 中添加针对 node_modules/@types 的特定条目:
{"compilerOptions": {"types": ["node","mocha"],"typeRoots": [// Keep whatever you had around before."./some-custom-types/",// You might need your local 'node_modules/@types'."./node_modules/@types",// You might also need to specify a shared 'node_modules/@types'// if you're using a "monorepo" layout."../../node_modules/@types",]}
}
以上就是 TypeScript 5.1 的稳定版本所更新的内容,此外,微软项目团队已经在努力开发 TypeScript 5.2 版本,如果你想知道更多关于 TypeScript 5.1 版本的信息,可查看发布说明。
参考链接:
https://devblogs.microsoft.com/typescript/announcing-typescript-5-1/
推荐阅读:
▶腾讯回应考生喊话马化腾;库克承认在使用 ChatGPT;OpenHarmony 4.0 Beta1 发布|极客头条
▶算网共生 云智无界 | 算网的新征程等你加入
▶AI 大战高考作文!实测 ChatGPT、文心一言、通义千问等 8 款“神器”