文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

TypeScript 5.0怎么使用

2023-07-05 14:13

关注

这篇文章主要介绍了TypeScript 5.0怎么使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇TypeScript 5.0怎么使用文章都会有所收获,下面我们一起来看看吧。

2023 年 3 月 17 日,TypeScript 5.0 正式发布!此版本带来了许多新功能,旨在使 TypeScript 更小、更简单、更快。TypeScript 5.0 实现了新的装饰器标准、更好地支持 Node 和打构建工具中的 ESM 项目的功能、库作者控制泛型推导的新方法、扩展了 JSDoc 功能、简化了配置,并进行了许多其他改进。

可以通过以下 npm 命令开始使用 TypeScript 5.0:

npm install -D typescript

以下是 TypeScript 5.0 的主要更新:

全新装饰器

装饰器是即将推出的 ECMAScript 特性,它允许我们以可重用的方式自定义类及其成员。

考虑以下代码:

class Person {    name: string;    constructor(name: string) {        this.name = name;    }    greet() {        console.log(`Hello, my name is ${this.name}.`);    }}const p = new Person("Ray");p.greet();

这里的 greet 方法很简单,在实际中它内部可能会跟复杂,比如需要执行异步逻辑,或者进行递归,亦或是有副作用等。那就可能需要使用 console.log 来调试 greet

class Person {    name: string;    constructor(name: string) {        this.name = name;    }    greet() {        console.log("LOG: Entering method.");        console.log(`Hello, my name is ${this.name}.`);        console.log("LOG: Exiting method.")    }}

如果有一种方法可以为每种方法做到这一点,可能会很好。

这就是装饰器的用武之地。我们可以编写一个名为 loggedMethod 的函数,如下所示:

function loggedMethod(originalMethod: any, _context: any) {    function replacementMethod(this: any, ...args: any[]) {        console.log("LOG: Entering method.")        const result = originalMethod.call(this, ...args);        console.log("LOG: Exiting method.")        return result;    }    return replacementMethod;}

这里用了很多 any,可以暂时忽略,这样可以让例子尽可能得简单。

这里,loggedMethod 需要传入一个参数(originalMethod) 并返回一个函数。执行过程如下:

现在我们就可以使用 loggedMethod 来修饰 greet 方法:

class Person {    name: string;    constructor(name: string) {        this.name = name;    }    @loggedMethod    greet() {        console.log(`Hello, my name is ${this.name}.`);    }}const p = new Person("Ray");p.greet();

输出如下:

LOG: Entering method.
Hello, my name is Ray.
LOG: Exiting method.

这里我们在 greet 上面使用了 loggedMethod 作为装饰器——注意这里的写法:@loggedMethod。这样,它会被原始方法和 context 对象调用。因为 loggedMethod 返回了一个新函数,该函数替换了 greet 的原始定义。

loggedMethod 的第二个参数被称为“ context 对象”,它包含一些关于如何声明装饰方法的有用信息——比如它是 #private 成员还是静态成员,或者方法的名称是什么。 下面来重写 loggedMethod 以利用它并打印出被修饰的方法的名称。

function loggedMethod(originalMethod: any, context: ClassMethodDecoratorContext) {    const methodName = String(context.name);    function replacementMethod(this: any, ...args: any[]) {        console.log(`LOG: Entering method '${methodName}'.`)        const result = originalMethod.call(this, ...args);        console.log(`LOG: Exiting method '${methodName}'.`)        return result;    }    return replacementMethod;}

TypeScript 提供了一个名为 ClassMethodDecoratorContext 的类型,它对方法装饰器采用的 context 对象进行建模。除了元数据之外,方法的 context 对象还有一个有用的函数:addInitializer。 这是一种挂接到构造函数开头的方法(如果使用静态方法,则挂接到类本身的初始化)。

举个例子,在JavaScript中,经常会写如下的模式:

class Person {    name: string;    constructor(name: string) {        this.name = name;        this.greet = this.greet.bind(this);    }    greet() {        console.log(`Hello, my name is ${this.name}.`);    }}

或者,greet可以声明为初始化为箭头函数的属性。

class Person {    name: string;    constructor(name: string) {        this.name = name;    }    greet = () => {        console.log(`Hello, my name is ${this.name}.`);    };}

编写这段代码是为了确保在greet作为独立函数调用或作为回调函数传递时不会重新绑定。

const greet = new Person("Ray").greet;greet();

可以编写一个装饰器,使用addInitializer在构造函数中为我们调用 bind

function bound(originalMethod: any, context: ClassMethodDecoratorContext) {    const methodName = context.name;    if (context.private) {        throw new Error(`'bound' cannot decorate private properties like ${methodName as string}.`);    }    context.addInitializer(function () {        this[methodName] = this[methodName].bind(this);    });}

bound不会返回任何内容,所以当它装饰一个方法时,它会保留原来的方法。相反,它会在其他字段初始化之前添加逻辑。

class Person {    name: string;    constructor(name: string) {        this.name = name;    }    @bound    @loggedMethod    greet() {        console.log(`Hello, my name is ${this.name}.`);    }}const p = new Person("Ray");const greet = p.greet;greet();

注意,我们使用了两个装饰器:@bound@loggedMethod。这些装饰是以“相反的顺序”运行的。也就是说,@loggedMethod修饰了原始方法greet@bound修饰了@loggedMethod的结果。在这个例子中,这没有关系——但如果装饰器有副作用或期望某种顺序,则可能有关系。

可以将这些装饰器放在同一行:

@bound @loggedMethod greet() {console.log(`Hello, my name is ${this.name}.`);}

我们甚至可以创建返回装饰器函数的函数。这使得我们可以对最终的装饰器进行一些自定义。如果我们愿意,我们可以让loggedMethod返回一个装饰器,并自定义它记录消息的方式。

function loggedMethod(headMessage = "LOG:") {    return function actualDecorator(originalMethod: any, context: ClassMethodDecoratorContext) {        const methodName = String(context.name);        function replacementMethod(this: any, ...args: any[]) {            console.log(`${headMessage} Entering method '${methodName}'.`)            const result = originalMethod.call(this, ...args);            console.log(`${headMessage} Exiting method '${methodName}'.`)            return result;        }        return replacementMethod;    }}

如果这样做,必须在使用loggedMethod作为装饰器之前调用它。然后,可以传入任何字符串作为记录到控制台的消息的前缀。

class Person {    name: string;    constructor(name: string) {        this.name = name;    }    @loggedMethod("")    greet() {        console.log(`Hello, my name is ${this.name}.`);    }}const p = new Person("Ray");p.greet();

输出结果如下:

Entering method 'greet'.Hello, my name is Ray.Exiting method 'greet'.

装饰器可不仅仅用于方法,还可以用于属性/字段、gettersetter和自动访问器。甚至类本身也可以装饰成子类化和注册。

上面的loggedMethodbound装饰器示例写的很简单,并省略了大量关于类型的细节。实际上,编写装饰器可能相当复杂。例如,上面的loggedMethod类型良好的版本可能看起来像这样:

function loggedMethod<This, Args extends any[], Return>(    target: (this: This, ...args: Args) => Return,    context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>) {    const methodName = String(context.name);    function replacementMethod(this: This, ...args: Args): Return {        console.log(`LOG: Entering method '${methodName}'.`)        const result = target.call(this, ...args);        console.log(`LOG: Exiting method '${methodName}'.`)        return result;    }    return replacementMethod;}

我们必须使用thisArgsreturn类型参数分别建模this、参数和原始方法的返回类型。

具体定义装饰器函数的复杂程度取决于想要保证什么。需要记住,装饰器的使用次数将超过它们的编写次数,所以类型良好的版本通常是更好的&mdash;&mdash;但显然与可读性有一个权衡,所以请尽量保持简单。

const 类型参数

当推断一个对象的类型时,TypeScript通常会选择一个通用类型。例如,在本例中,names 的推断类型是string[]

type HasNames = { readonly names: string[] };function getNamesExactly<T extends HasNames>(arg: T): T["names"] {    return arg.names;}// names 的推断类型为 string[]const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});

通常这样做的目的是实现突变。然而,根据getnames确切的作用以及它的使用方式,通常情况下需要更具体的类型。到目前为止,通常不得不在某些地方添加const,以实现所需的推断:

// 我们想要的类型: readonly ["Alice", "Bob", "Eve"]// 我们得到的类型: string[]const names1 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});// 得到想要的类型:readonly ["Alice", "Bob", "Eve"]const names2 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]} as const);

这写起来会很麻烦,也很容易忘记。在 TypeScript 5.0 中,可以在类型参数声明中添加const修饰符,从而使类const推断成为默认值:

type HasNames = { names: readonly string[] };function getNamesExactly<const T extends HasNames>(arg: T): T["names"] {//                       ^^^^^    return arg.names;}// 推断类型:readonly ["Alice", "Bob", "Eve"]// 注意,这里不需要再写 as constconst names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] });

注意,const修饰符并不排斥可变值,也不需要不可变约束。使用可变类型约束可能会得到意外的结果。例如:

declare function fnBad<const T extends string[]>(args: T): void;// T仍然是string[],因为readonly ["a", "b", "c"]不能赋值给string[]fnBad(["a", "b" ,"c"]);

这里,T的推断候选值是readonly ["a", "b", "c"],而readonly数组不能用于需要可变数组的地方。在这种情况下,推理回退到约束,数组被视为string[],调用仍然成功进行。

更好的定义应该使用readonly string[]:

declare function fnGood<const T extends readonly string[]>(args: T): void;// T 是 readonly ["a", "b", "c"]fnGood(["a", "b" ,"c"]);

同样,要记住,const修饰符只影响在调用中编写的对象、数组和基本类型表达式的推断,所以不会(或不能)用const修饰的参数将看不到任何行为的变化:

declare function fnGood<const T extends readonly string[]>(args: T): void;const arr = ["a", "b" ,"c"];//  T 仍然是 string[],const 修饰符没有作用fnGood(arr);

extends 支持多配置文件

当管理多个项目时,通常每个项目的 tsconfig.json 文件都会继承于基础配置。这就是为什么TypeScript支持extends字段,用于从compilerOptions中复制字段。

// packages/front-end/src/tsconfig.json{    "extends": "../../../tsconfig.base.json",    "compilerOptions": {        "outDir": "../lib",        // ...    }}

但是,在某些情况下,可能希望从多个配置文件进行扩展。例如,想象一下使用一个TypeScript 基本配置文件到 npm。如果想让所有的项目也使用npm中@tsconfig/strictest包中的选项,那么有一个简单的解决方案:将tsconfig.base.json扩展到@tsconfig/strictest

// tsconfig.base.json{    "extends": "@tsconfig/strictest/tsconfig.json",    "compilerOptions": {        // ...    }}

这在一定程度上是有效的。 如果有任何项目不想使用 @tsconfig/strictest,就必须手动禁用这些选项,或者创建一个不从 @tsconfig/strictest 扩展的单独版本的 tsconfig.base.json

为了提供更多的灵活性,Typescript 5.0 允许extends字段接收多个项。例如,在这个配置文件中:

{    "extends": ["a", "b", "c"],    "compilerOptions": {        // ...    }}

这样写有点像直接扩展 c,其中 c 扩展 b,b 扩展 a。 如果任何字段“冲突”,则后一个项生效。

所以在下面的例子中,strictNullChecksnoImplicitAny 都会在最终的 tsconfig.json 中启用。

// tsconfig1.json{    "compilerOptions": {        "strictNullChecks": true    }}// tsconfig2.json{    "compilerOptions": {        "noImplicitAny": true    }}// tsconfig.json{    "extends": ["./tsconfig1.json", "./tsconfig2.json"],    "files": ["./index.ts"]}

可以用下面的方式重写最上面的例子:

// packages/front-end/src/tsconfig.json{    "extends": ["@tsconfig/strictest/tsconfig.json", "../../../tsconfig.base.json"],    "compilerOptions": {        "outDir": "../lib",        // ...    }}

所有枚举都是联合枚举

当 TypeScript 最初引入枚举时,它只不过是一组具有相同类型的数值常量:

enum E {    Foo = 10,    Bar = 20,}

E.Foo 和 E.Bar 唯一的特别之处在于它们可以分配给任何期望类型 E 的东西。除此之外,它们只是数字。

function takeValue(e: E) {}takeValue(E.Foo); // ✅takeValue(123);   // ❌

直到 TypeScript 2.0 引入了枚举字面量类型,它赋予每个枚举成员自己的类型,并将枚举本身转换为每个成员类型的联合。它还允许我们只引用枚举类型的一个子集,并缩小这些类型。

// Color就像是一个联合:Red | Orange | Yellow | Green | Blue | Violetenum Color {    Red, Orange, Yellow, Green, Blue, , Violet}// 每个枚举成员都有自己的类型,可以引用type PrimaryColor = Color.Red | Color.Green | Color.Blue;function isPrimaryColor(c: Color): c is PrimaryColor {    // 缩小字面量类型可以捕获bug// TypeScript在这里会报错,因为// 最终会比较 Color.Red 和 Color.Green。// 本想使用||,但不小心写了&&    return c === Color.Red && c === Color.Green && c === Color.Blue;}

给每个枚举成员指定自己的类型有一个问题,即这些类型在某种程度上与成员的实际值相关联。在某些情况下,这个值是不可能计算出来的&mdash;&mdash;例如,枚举成员可以通过函数调用进行初始化。

enum E {    Blah = Math.random()}

每当TypeScript遇到这些问题时,它都会悄无声息地退出并使用旧的枚举策略。这意味着要放弃并集和字面量类型的所有优点。

TypeScript 5.0 通过为每个计算成员创建唯一的类型,设法将所有枚举转换为联合枚举。这意味着现在可以缩小所有枚举的范围,并将其成员作为类型引用。

--moduleResolution

TypeScript 4.7 为 --module--moduleResolution 设置引入了 node16 和 nodenext 选项。这些选项的目的是更好地模拟 Node.js 中 ECMAScript 模块的精确查找规则; 然而,这种模式有许多其他工具没有真正执行的限制。

例如,在 Node.js 的 ECMAScript 模块中,任何相对导入都需要包含文件扩展名。

// entry.mjsimport * as utils from "./utils";     //  ❌ - 需要包括文件扩展名。import * as utils from "./utils.mjs"; //  ✅

在Node.js和浏览器中这样做是有原因的&mdash;&mdash;它使文件查找更快,并且更适合原始文件服务器。但对于许多使用打包工具的开发人员来说,node16/nodenext 的设置很麻烦,因为打包工具没有这些限制中的大部分。在某些方面,node解析模式更适合使用打包工具的人。

但在某些方面,原有的 node 解析模式已经过时了。 大多数现代打包工具在 Node.js 中使用 ECMAScript 模块和 CommonJS 查找规则的融合。

为了模拟打包工具是如何工作的,TypeScript 5.0 引入了一个新策略:--moduleResolution bundler

{    "compilerOptions": {        "target": "esnext",        "moduleResolution": "bundler"    }}

如果正在使用现代打包工具,如 Vite、esbuild、swc、Webpack、Parcel 或其他实现混合查找策略的打包工具,那么新的 bundler 选项应该非常适合你。

另一方面,如果正在编写一个打算在 npm 上发布的库,使用bundler选项可以隐藏不使用bundler的用户可能出现的兼容性问题。因此,在这些情况下,使用node16nodenext解析选项可能是更好的方法。

自定义解析标志

JavaScript 工具现在可以模拟“混合”解析规则,就像上面描述的打包工具模式一样。 由于工具的支持可能略有不同,TypeScript 5.0 提供了启用或禁用一些功能的方法。

allowImportingTsExtensions

--allowImportingTsExtensions 允许 TypeScript 文件使用特定于 TypeScript 的扩展名(如 .ts.mts.tsx)相互导入。

仅当启用 --noEmit--emitDeclarationOnly 时才允许使用此标志,因为这些导入路径在运行时无法在 JavaScript 输出文件中解析。 这里的期望是解析器(例如打包工具、运行时或其他工具)将使 .ts 文件之间的这些导入正常工作。

resolvePackageJsonExports

--resolvePackageJsonExports 强制 TypeScript 在从 node_modules 中的包中读取时查询 package.json 文件的 exports 字段。

resolvePackageJsonImports

--resolvePackageJsonImports 强制 TypeScript 在从其祖先目录包含 package.json 的文件执行以 # 开头的查找时查询 package.json 文件的 imports 字段。

--moduleResolutionnode16nodenextbundler 选项下,此选项默认为 true。

allowArbitraryExtensions

在 TypeScript 5.0 中,当导入路径以不是已知 JavaScript 或 TypeScript 文件扩展名的扩展名结尾时,编译器将以 {file basename}.d.{extension} 的形式查找该路径的声明文件。例如,如果在打包项目中使用 CSS loader,可能希望为这些样式表编写(或生成)声明文件:

.cookie-banner {  display: none;}
// app.d.css.tsdeclare const css: {  cookieBanner: string;};export default css;
// App.tsximport styles from "./app.css";styles.cookieBanner; // string

默认情况下,这个导入将引发一个错误,让你知道TypeScript不理解这个文件类型,你的运行时可能不支持导入它。但是,如果已经配置了运行时或打包工具来处理它,则可以使用新--allowArbitraryExtensions编译器选项来抑制错误。

注意,可以通过添加一个名为 app.css.d.ts 而不是 app.d.css.ts 的声明文件通常可以实现类似的效果。然而,这只是通过 Node 对 CommonJS 的 require 解析规则实现的。严格来说,前者被解释为一个名为 app.css.js 的 JavaScript 文件的声明文件。 因为相关文件导入需要在 Node 的 ESM 支持中包含扩展名,所以在我们的例子中,TypeScript 会在 --moduleResolution node16 或 nodenext 下的 ESM 文件中出错。

customConditions

--customConditions 获取当 TypeScript 从 package.json 的 [exports] 或 (nodejs.org/api/package&hellip;) 或 imports 字段解析时应该成功的附加的条件列表。这些条件将添加到解析器默认使用的现有条件中。

例如,当此字段在 tsconfig.json 中设置为:

{    "compilerOptions": {        "target": "es2022",        "moduleResolution": "bundler",        "customConditions": ["my-condition"]    }}

任何时候在 package.json 中引用 exports 或 imports 字段时,TypeScript 都会考虑名为 my-condition 的条件。

因此,当从具有以下 package.json 的包中导入时:

{    // ...    "exports": {        ".": {            "my-condition": "./foo.mjs",            "node": "./bar.mjs",            "import": "./baz.mjs",            "require": "./biz.mjs"        }    }}

TypeScript 将尝试查找与foo.mjs对应的文件。这个字段只有在 node16、nodenext 和--modulerresolution为 bundler 时才有效。

--verbatimModuleSyntax

默认情况下,TypeScript 会执行一些称为导入省略的操作。如果这样写:

import { Car } from "./car";export function drive(car: Car) {    // ...}

TypeScript 检测到只对类型使用导入并完全删除导入。输出 JavaScript 可能是这样的:

export function drive(car) {    // ...}

大多数时候这很好,因为如果 Car 不是从 ./car 导出的值,将得到一个运行时错误。但对于某些边界情况,它确实增加了一层复杂性。例如,没有像 import "./car" 这样的语句,即完全放弃了 import,这实际上对有无副作用的模块产生影响。

TypeScript 的 JavaScript emit 策略也有另外几层复杂性&mdash;&mdash;省略导入并不总是由如何使用 import 驱动的,它通常还会参考值的声明方式。所以并不总是很清楚是否像下面这样的代码:

export { Car } from "./car";

如果 Car 是用类之类的东西声明的,那么它可以保存在生成的 JavaScript 文件中。 但是,如果 Car 仅声明为类型别名或接口,则 JavaScript 文件不应导出 Car。

虽然 TypeScript 可能能够根据来自跨文件的信息做出这些发出决策,但并非每个编译器都可以。

imports 和 exports 的类型修饰符在这些情况下会有帮助。我们可以明确指定importexport仅用于类型分析,并且可以在JavaScript文件中使用类型修饰符完全删除。

// 这条语句可以在JS输出中完全删除import type * as car from "./car";// 在JS输出中可以删除命名的import/export Carimport { type Car } from "./car";export { type Car } from "./car";

类型修饰符本身并不是很有用&mdash;&mdash;默认情况下,模块省略仍然会删除导入,并且没有强制区分类型和普通导入和导出。 因此 TypeScript 有标志 --importsNotUsedAsValues 以确保使用 type 修饰符,--preserveValueImports 以防止某些模块省略行为,以及 --isolatedModules 以确保 TypeScript 代码适用于不同的编译器。 不幸的是,很难理解这 3 个标志的细节,并且仍然存在一些具有意外行为的边界情况。

TypeScript 5.0 引入了一个名为 --verbatimModuleSyntax 的新选项来简化这种情况。规则要简单得多,任何没有 type 修饰符的导入或导出都会被保留。任何使用 type 修饰符的内容都会被完全删除。

// 完全被删除import type { A } from "a";// 重写为 'import { b } from "bcd";'import { b, type c, type d } from "bcd";// 重写为 'import {} from "xyz";'import { type xyz } from "xyz";

有了这个新选项,所见即所得。不过,当涉及到模块互操作时,这确实有一些影响。 在此标志下,当设置或文件扩展名暗示不同的模块系统时,ECMAScript 导入和导出不会被重写为 require 调用。相反,会得到一个错误。 如果需要生成使用 requiremodule.exports 的代码,则必须使用早于 ES2015 的 TypeScript 模块语法:

TypeScript 5.0怎么使用

虽然这是一个限制,但它确实有助于使一些问题更加明显。 例如,忘记在 --module node16 下的 package.json 中设置 type 字段是很常见的。 因此,开发人员会在没有意识到的情况下开始编写 CommonJS 模块而不是 ES 模块,从而给出意外的查找规则和 JavaScript 输出。 这个新标志确保有意使用正在使用的文件类型,因为语法是有意不同的。

因为 --verbatimModuleSyntax 提供了比 --importsNotUsedAsValues--preserveValueImports 更一致的作用,所以这两个现有标志被弃用了。

支持 export type *

当 TypeScript 3.8 引入仅类型导入时,新语法不允许在 export * from "module" 或 export * as ns from "module" 重新导出时使用。 TypeScript 5.0 添加了对这两种形式的支持:

// models/vehicles.tsexport class Spaceship {  // ...}// models/index.tsexport type * as vehicles from "./vehicles";// main.tsimport { vehicles } from "./models";function takeASpaceship(s: vehicles.Spaceship) {  //  ✅}function makeASpaceship() {  return new vehicles.Spaceship();  //         ^^^^^^^^  // vehicles 不能用作值,因为它是使用“export type”导出的。}

JSDoc 支持 @satisfies

TypeScript 4.9 引入了 satisfies 操作符。它确保表达式的类型是兼容的,而不影响类型本身。以下面的代码为例:

interface CompilerOptions {    strict?: boolean;    outDir?: string;    // ...}interface ConfigSettings {    compilerOptions?: CompilerOptions;    extends?: string | string[];    // ...}let myConfigSettings = {    compilerOptions: {        strict: true,        outDir: "../lib",        // ...    },    extends: [        "@tsconfig/strictest/tsconfig.json",        "../../../tsconfig.base.json"    ],} satisfies ConfigSettings;

这里,TypeScript 知道 myCompilerOptions.extends 是用数组声明的,因为虽然 satisfies 验证了对象的类型,但它并没有直接将其更改为 CompilerOptions 而丢失信息。所以如果想映射到 extends 上,是可以的。

declare function resolveConfig(configPath: string): CompilerOptions;let inheritedConfigs = myConfigSettings.extends.map(resolveConfig);

这对 TypeScript 用户很有帮助,但是很多人使用 TypeScript 来使用 JSDoc 注释对 JavaScript 代码进行类型检查。 这就是为什么 TypeScript 5.0 支持一个名为 @satisfies 的新 JSDoc 标签,它做的事情完全一样。

可以捕获类型不匹配:

// @ts-checklet myCompilerOptions = {    outdir: "../lib",//  ~~~~~~ oops! we meant outDir};

但它会保留表达式的原始类型,允许稍后在代码中更精确地使用值。

// @ts-checklet myConfigSettings = {    compilerOptions: {        strict: true,        outDir: "../lib",    },    extends: [        "@tsconfig/strictest/tsconfig.json",        "../../../tsconfig.base.json"    ],};let inheritedConfigs = myConfigSettings.extends.map(resolveConfig);

也可以内嵌在任何带括号的表达式上。 可以这样写 myCompilerOptions

let myConfigSettings =  ({    compilerOptions: {        strict: true,        outDir: "../lib",    },    extends: [        "@tsconfig/strictest/tsconfig.json",        "../../../tsconfig.base.json"    ],});

这可能在函数调用时更有意义:

compileCode( ({    // ...}));

JSDoc 支持 @overload

在 TypeScript 中,可以为函数指定重载。 重载提供了一种方式,用不同的参数调用一个函数,并返回不同的结果。它可以限制调用者实际使用函数的方式,并优化将返回的结果。

// 重载:function printValue(str: string): void;function printValue(num: number, maxFractionDigits?: number): void;// 实现:function printValue(value: string | number, maximumFractionDigits?: number) {    if (typeof value === "number") {        const formatter = Intl.NumberFormat("en-US", {            maximumFractionDigits,        });        value = formatter.format(value);    }    console.log(value);}

这里,printValue 将字符串或数字作为第一个参数。如果它需要一个数字,它可以使用第二个参数来确定可以打印多少个小数位。

TypeScript 5.0 现在允许 JSDoc 使用新的 @overload 标签声明重载。 每个带有 @overload标签的 JSDoc 注释都被视为以下函数声明的不同重载。

// @ts-checkfunction printValue(value, maximumFractionDigits) {    if (typeof value === "number") {        const formatter = Intl.NumberFormat("en-US", {            maximumFractionDigits,        });        value = formatter.format(value);    }    console.log(value);}

现在,无论是在 TypeScript 还是 JavaScript 文件中编写,TypeScript 都可以让我们知道是否错误地调用了函数。

printValue("hello!");printValue(123.45);printValue(123.45, 2);printValue("hello!", 123); // ❌

编辑器中不区分大小写的导入排序

在 Visual Studio 和 VS Code 等编辑器中,TypeScript 支持组织和排序导入和导出的体验。 但是,对于列表何时“排序”,通常会有不同的解释。

例如,下面的导入列表是否排序?

import {    Toggle,    freeze,    toBoolean,} from "./utils";

答案可能是“视情况而定”。 如果不关心区分大小写,那么这个列表显然没有排序。 字母 f 出现在 t 和 T 之前。

但在大多数编程语言中,排序默认是比较字符串的字节值。JavaScript 比较字符串的方式意味着“Toggle”总是在“freeze”之前,因为根据 ASCII 字符编码,大写字母在小写字母之前。 所以从这个角度来看,导入列表是已排序的。

TypeScript 之前认为导入列表是已排序的,因为它会做基本的区分大小写的排序。 对于喜欢不区分大小写排序的开发人员,或者使用像 ESLint 这样默认需要不区分大小写排序的工具的开发人员来说,这可能是一个阻碍。

TypeScript 现在默认检测大小写。这意味着 TypeScript 和 ESLint 等工具通常不会就如何最好地对导入进行排序而相互“斗争”。

这些选项最终可能由编辑器配置。目前,它们仍然不稳定且处于试验阶段,现在可以通过在 JSON 选项中使用 typescript.unstable 在 VS Code 中选择加入它们。 以下是可以尝试的所有选项(设置为默认值):

{    "typescript.unstable": {        // Should sorting be case-sensitive? Can be:        // - true        // - false        // - "auto" (auto-detect)        "organizeImportsIgnoreCase": "auto",        // Should sorting be "ordinal" and use code points or consider Unicode rules? Can be:        // - "ordinal"        // - "unicode"        "organizeImportsCollation": "ordinal",        // Under `"organizeImportsCollation": "unicode"`,        // what is the current locale? Can be:        // - [any other locale code]        // - "auto" (use the editor's locale)        "organizeImportsLocale": "en",        // Under `"organizeImportsCollation": "unicode"`,        // should upper-case letters or lower-case letters come first? Can be:        // - false (locale-specific)        // - "upper"        // - "lower"        "organizeImportsCaseFirst": false,        // Under `"organizeImportsCollation": "unicode"`,        // do runs of numbers get compared numerically (i.e. "a1" < "a2" < "a100")? Can be:        // - true        // - false        "organizeImportsNumericCollation": true,        // Under `"organizeImportsCollation": "unicode"`,        // do letters with accent marks/diacritics get sorted distinctly        // from their "base" letter (i.e. is é different from e)? Can be        // - true        // - false        "organizeImportsAccentCollation": true    },    "javascript.unstable": {        // same options valid here...    },}

完善 switch/case

在编写 switch 语句时,TypeScript 现在会检测被检查的值何时具有字面量类型。以提供更便利的代码快捷输入:

TypeScript 5.0怎么使用

速度、内存和包大小优化

TypeScript 5.0 在代码结构、数据结构和算法实现中包含许多强大的变化。这些都意味着整个体验应该更快&mdash;&mdash;不仅仅是运行 TypeScript,甚至安装它。

以下是相对于 TypeScript 4.9 在速度和大小方面的优势:

场景时间或大小相对于 TS 4.9
material-ui 构建时间90%
TypeScript 编译器启动时间89%
Playwright 构建时间88%
TypeScript 编译器自构建时间87%
Outlook Web 构建时间82%
VS Code 构建时间80%
TypeScript npm 包大小59%

图表形式:

TypeScript 5.0怎么使用

TypeScript 包大小变化:

TypeScript 5.0怎么使用

那为什么会有如此大的提升呢?部分优化细节如下:

首先,将 TypeScript 从命名空间迁移到模块,这样就能够利用现代构建工具来执行优化。重新审视了打包策略并删除一些已弃用的代码,已将 TypeScript 4.9 的 63.8 MB 包大小减少了约 26.4 MB。还通过直接函数调用带来了显著的速度提升。

在将信息序列化为字符串时,执行了一些缓存。 类型显示可能作为错误报告、声明触发、代码补全等的一部分发生,最终可能会相当昂贵。TypeScript 现在缓存了一些常用的机制以在这些操作中重用。

总的来说,预计大多数代码库应该会看到 TypeScript 5.0 的速度提升,并且始终能够重现 10% 到 20% 之间的提升。当然,这将取决于硬件和代码库特性。

其他重大更改和弃用

运行时要求

TypeScript 现在的 target 是 ECMAScript 2018。TypeScript 软件包还将预期的最低引擎版本设置为 12.20。对于 Node.js 用户来说,这意味着 TypeScript 5.0 需要至少Node.js 12.20 或更高版本才能运行。

lib.d.ts 变化

更改 DOM 类型的生成方式可能会对现有代码产生影响。注意,某些属性已从数字转换为数字字面量类型,并且用于剪切、复制和粘贴事件处理的属性和方法已跨接口移动。

API 重大变更

在 TypeScript 5.0 中, 转向了模块,删除了一些不必要的接口,并进行了一些正确性改进。

关系运算符中的禁止隐式强制

如果编写的代码可能导致隐式字符串到数字的强制转换,TypeScript 中的某些操作现在会进行警告:

function func(ns: number | string) {  return ns * 4; // 错误,可能存在隐式强制转换}

在 5.0 中,这也将应用于关系运算符 >、<、<= 和 >=:

function func(ns: number | string) {  return ns > 4;}

如果需要这样做,可以使用+显式地将操作数转换为数字:

function func(ns: number | string) {  return +ns > 4; // OK}

弃用和默认更改

在 TypeScript 5.0 中,弃用了以下设置和设置值:

在 TypeScript 5.5 之前,这些配置将继续被允许使用,届时它们将被完全删除,但是,如果正在使用这些设置,将收到警告。 在 TypeScript 5.0 以及未来版本 5.1、5.2、5.3 和 5.4 中,可以指定 "ignoreDeprecations": "5.0" 以消除这些警告。 很快会发布一个 4.9 补丁,允许指定 ignoreDeprecations 以实现更平滑的升级。除了弃用之外,还更改了一些设置以更好地改进 TypeScript 中的跨平台行为。

关于“TypeScript 5.0怎么使用”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“TypeScript 5.0怎么使用”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯