" />

欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

TypeScript?5.0?正式發(fā)布及使用指南詳解

 更新時(shí)間:2023年03月21日 08:30:10   作者:CUGGZ  
這篇文章主要為大家介紹了TypeScript?5.0?正式發(fā)布及使用指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

2023 年 3 月 17 日,TypeScript 5.0 正式發(fā)布!此版本帶來(lái)了許多新功能,旨在使 TypeScript 更小、更簡(jiǎn)單、更快。TypeScript 5.0 實(shí)現(xiàn)了新的裝飾器標(biāo)準(zhǔn)、更好地支持 Node 和打構(gòu)建工具中的 ESM 項(xiàng)目的功能、庫(kù)作者控制泛型推導(dǎo)的新方法、擴(kuò)展了 JSDoc 功能、簡(jiǎn)化了配置,并進(jìn)行了許多其他改進(jìn)。

可以通過(guò)以下 npm 命令開(kāi)始使用 TypeScript 5.0:

npm install -D typescript

以下是 TypeScript 5.0 的主要更新:

  • 全新裝飾器
  • const 類(lèi)型參數(shù)
  • extends 支持多配置文件
  • 所有枚舉都是聯(lián)合枚舉
  • --moduleResolutionbundler
  • 自定義解析標(biāo)志
  • --verbatimModuleSyntax
  • 支持 export type *
  • JSDoc 支持 @satisfies
  • JSDoc 支持 @overload
  • 編輯器中不區(qū)分大小寫(xiě)的導(dǎo)入排序
  • 完善 switch/case
  • 優(yōu)化速度、內(nèi)存和包大小
  • 其他重大更改和棄用

全新裝飾器

裝飾器是即將推出的 ECMAScript 特性,它允許我們以可重用的方式自定義類(lèi)及其成員。

考慮以下代碼:

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 方法很簡(jiǎn)單,在實(shí)際中它內(nèi)部可能會(huì)跟復(fù)雜,比如需要執(zhí)行異步邏輯,或者進(jìn)行遞歸,亦或是有副作用等。那就可能需要使用 console.log 來(lái)調(diào)試 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.")
    }
}

如果有一種方法可以為每種方法做到這一點(diǎn),可能會(huì)很好。

這就是裝飾器的用武之地。我們可以編寫(xiě)一個(gè)名為 loggedMethod 的函數(shù),如下所示:

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,可以暫時(shí)忽略,這樣可以讓例子盡可能得簡(jiǎn)單。

這里,loggedMethod 需要傳入一個(gè)參數(shù)(originalMethod) 并返回一個(gè)函數(shù)。執(zhí)行過(guò)程如下:

  • 打?。篖OG: Entering method.
  • 將 this 及其所有參數(shù)傳遞給原始方法
  • 打?。篖OG: Exiting method.
  • 返回原始方法的執(zhí)行結(jié)果

現(xiàn)在我們就可以使用 loggedMethod 來(lái)修飾 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.

這里我們?cè)?greet 上面使用了 loggedMethod 作為裝飾器——注意這里的寫(xiě)法:@loggedMethod。這樣,它會(huì)被原始方法和 context 對(duì)象調(diào)用。因?yàn)?loggedMethod 返回了一個(gè)新函數(shù),該函數(shù)替換了 greet 的原始定義。

loggedMethod 的第二個(gè)參數(shù)被稱(chēng)為“ context 對(duì)象”,它包含一些關(guān)于如何聲明裝飾方法的有用信息——比如它是 #private 成員還是靜態(tài)成員,或者方法的名稱(chēng)是什么。 下面來(lái)重寫(xiě) loggedMethod 以利用它并打印出被修飾的方法的名稱(chēng)。

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 提供了一個(gè)名為 ClassMethodDecoratorContext 的類(lèi)型,它對(duì)方法裝飾器采用的 context 對(duì)象進(jìn)行建模。除了元數(shù)據(jù)之外,方法的 context 對(duì)象還有一個(gè)有用的函數(shù):addInitializer。 這是一種掛接到構(gòu)造函數(shù)開(kāi)頭的方法(如果使用靜態(tài)方法,則掛接到類(lèi)本身的初始化)。

舉個(gè)例子,在JavaScript中,經(jīng)常會(huì)寫(xiě)如下的模式:

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可以聲明為初始化為箭頭函數(shù)的屬性。

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

編寫(xiě)這段代碼是為了確保在greet作為獨(dú)立函數(shù)調(diào)用或作為回調(diào)函數(shù)傳遞時(shí)不會(huì)重新綁定。

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

可以編寫(xiě)一個(gè)裝飾器,使用addInitializer在構(gòu)造函數(shù)中為我們調(diào)用 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不會(huì)返回任何內(nèi)容,所以當(dāng)它裝飾一個(gè)方法時(shí),它會(huì)保留原來(lái)的方法。相反,它會(huì)在其他字段初始化之前添加邏輯。

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();

注意,我們使用了兩個(gè)裝飾器:@bound@loggedMethod。這些裝飾是以“相反的順序”運(yùn)行的。也就是說(shuō),@loggedMethod修飾了原始方法greet, @bound修飾了@loggedMethod的結(jié)果。在這個(gè)例子中,這沒(méi)有關(guān)系——但如果裝飾器有副作用或期望某種順序,則可能有關(guān)系。

可以將這些裝飾器放在同一行:

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

我們甚至可以創(chuàng)建返回裝飾器函數(shù)的函數(shù)。這使得我們可以對(duì)最終的裝飾器進(jìn)行一些自定義。如果我們?cè)敢?,我們可以?code>loggedMethod返回一個(gè)裝飾器,并自定義它記錄消息的方式。

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作為裝飾器之前調(diào)用它。然后,可以傳入任何字符串作為記錄到控制臺(tái)的消息的前綴。

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();

輸出結(jié)果如下:

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

裝飾器可不僅僅用于方法,還可以用于屬性/字段、getter、setter和自動(dòng)訪問(wèn)器。甚至類(lèi)本身也可以裝飾成子類(lèi)化和注冊(cè)。

上面的loggedMethodbound裝飾器示例寫(xiě)的很簡(jiǎn)單,并省略了大量關(guān)于類(lèi)型的細(xì)節(jié)。實(shí)際上,編寫(xiě)裝飾器可能相當(dāng)復(fù)雜。例如,上面的loggedMethod類(lèi)型良好的版本可能看起來(lái)像這樣:

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;
}

我們必須使用this、Argsreturn類(lèi)型參數(shù)分別建模this、參數(shù)和原始方法的返回類(lèi)型。

具體定義裝飾器函數(shù)的復(fù)雜程度取決于想要保證什么。需要記住,裝飾器的使用次數(shù)將超過(guò)它們的編寫(xiě)次數(shù),所以類(lèi)型良好的版本通常是更好的——但顯然與可讀性有一個(gè)權(quán)衡,所以請(qǐng)盡量保持簡(jiǎn)單。

const 類(lèi)型參數(shù)

當(dāng)推斷一個(gè)對(duì)象的類(lèi)型時(shí),TypeScript通常會(huì)選擇一個(gè)通用類(lèi)型。例如,在本例中,names 的推斷類(lèi)型是string[]

type HasNames = { readonly names: string[] };
function getNamesExactly<T extends HasNames>(arg: T): T["names"] {
    return arg.names;
}
// names 的推斷類(lèi)型為 string[]
const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});

通常這樣做的目的是實(shí)現(xiàn)突變。然而,根據(jù)getnames確切的作用以及它的使用方式,通常情況下需要更具體的類(lèi)型。到目前為止,通常不得不在某些地方添加const,以實(shí)現(xiàn)所需的推斷:

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

這寫(xiě)起來(lái)會(huì)很麻煩,也很容易忘記。在 TypeScript 5.0 中,可以在類(lèi)型參數(shù)聲明中添加const修飾符,從而使類(lèi)const推斷成為默認(rèn)值:

type HasNames = { names: readonly string[] };
function getNamesExactly<const T extends HasNames>(arg: T): T["names"] {
//                       ^^^^^
    return arg.names;
}
// 推斷類(lèi)型:readonly ["Alice", "Bob", "Eve"]
// 注意,這里不需要再寫(xiě) as const
const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] });

注意,const修飾符并不排斥可變值,也不需要不可變約束。使用可變類(lèi)型約束可能會(huì)得到意外的結(jié)果。例如:

declare function fnBad<const T extends string[]>(args: T): void;
// T仍然是string[],因?yàn)閞eadonly ["a", "b", "c"]不能賦值給string[]
fnBad(["a", "b" ,"c"]);

這里,T的推斷候選值是readonly ["a", "b", "c"],而readonly數(shù)組不能用于需要可變數(shù)組的地方。在這種情況下,推理回退到約束,數(shù)組被視為string[],調(diào)用仍然成功進(jìn)行。

更好的定義應(yīng)該使用readonly string[]:

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

同樣,要記住,const修飾符只影響在調(diào)用中編寫(xiě)的對(duì)象、數(shù)組和基本類(lèi)型表達(dá)式的推斷,所以不會(huì)(或不能)用const修飾的參數(shù)將看不到任何行為的變化:

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

extends 支持多配置文件

當(dāng)管理多個(gè)項(xiàng)目時(shí),通常每個(gè)項(xiàng)目的 tsconfig.json 文件都會(huì)繼承于基礎(chǔ)配置。這就是為什么TypeScript支持extends字段,用于從compilerOptions中復(fù)制字段。

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

但是,在某些情況下,可能希望從多個(gè)配置文件進(jìn)行擴(kuò)展。例如,想象一下使用一個(gè)TypeScript 基本配置文件到 npm。如果想讓所有的項(xiàng)目也使用npm中@tsconfig/strictest包中的選項(xiàng),那么有一個(gè)簡(jiǎn)單的解決方案:將tsconfig.base.json擴(kuò)展到@tsconfig/strictest

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

這在一定程度上是有效的。 如果有任何項(xiàng)目不想使用 @tsconfig/strictest,就必須手動(dòng)禁用這些選項(xiàng),或者創(chuàng)建一個(gè)不從 @tsconfig/strictest 擴(kuò)展的單獨(dú)版本的 tsconfig.base.json。

為了提供更多的靈活性,Typescript 5.0 允許extends字段接收多個(gè)項(xiàng)。例如,在這個(gè)配置文件中:

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

這樣寫(xiě)有點(diǎn)像直接擴(kuò)展 c,其中 c 擴(kuò)展 b,b 擴(kuò)展 a。 如果任何字段“沖突”,則后一個(gè)項(xiàng)生效。

所以在下面的例子中,strictNullChecksnoImplicitAny 都會(huì)在最終的 tsconfig.json 中啟用。

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

可以用下面的方式重寫(xiě)最上面的例子:

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

所有枚舉都是聯(lián)合枚舉

當(dāng) TypeScript 最初引入枚舉時(shí),它只不過(guò)是一組具有相同類(lèi)型的數(shù)值常量:

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

E.Foo 和 E.Bar 唯一的特別之處在于它們可以分配給任何期望類(lèi)型 E 的東西。除此之外,它們只是數(shù)字。

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

直到 TypeScript 2.0 引入了枚舉字面量類(lèi)型,它賦予每個(gè)枚舉成員自己的類(lèi)型,并將枚舉本身轉(zhuǎn)換為每個(gè)成員類(lèi)型的聯(lián)合。它還允許我們只引用枚舉類(lèi)型的一個(gè)子集,并縮小這些類(lèi)型。

// Color就像是一個(gè)聯(lián)合:Red | Orange | Yellow | Green | Blue | Violet
enum Color {
    Red, Orange, Yellow, Green, Blue, /* Indigo */, Violet
}
// 每個(gè)枚舉成員都有自己的類(lèi)型,可以引用
type PrimaryColor = Color.Red | Color.Green | Color.Blue;
function isPrimaryColor(c: Color): c is PrimaryColor {
    // 縮小字面量類(lèi)型可以捕獲bug
		// TypeScript在這里會(huì)報(bào)錯(cuò),因?yàn)?
		// 最終會(huì)比較 Color.Red 和 Color.Green。
		// 本想使用||,但不小心寫(xiě)了&&
    return c === Color.Red && c === Color.Green && c === Color.Blue;
}

給每個(gè)枚舉成員指定自己的類(lèi)型有一個(gè)問(wèn)題,即這些類(lèi)型在某種程度上與成員的實(shí)際值相關(guān)聯(lián)。在某些情況下,這個(gè)值是不可能計(jì)算出來(lái)的——例如,枚舉成員可以通過(guò)函數(shù)調(diào)用進(jìn)行初始化。

enum E {
    Blah = Math.random()
}

每當(dāng)TypeScript遇到這些問(wèn)題時(shí),它都會(huì)悄無(wú)聲息地退出并使用舊的枚舉策略。這意味著要放棄并集和字面量類(lèi)型的所有優(yōu)點(diǎn)。

TypeScript 5.0 通過(guò)為每個(gè)計(jì)算成員創(chuàng)建唯一的類(lèi)型,設(shè)法將所有枚舉轉(zhuǎn)換為聯(lián)合枚舉。這意味著現(xiàn)在可以縮小所有枚舉的范圍,并將其成員作為類(lèi)型引用。

--moduleResolution

TypeScript 4.7 為 --module--moduleResolution 設(shè)置引入了 node16 和 nodenext 選項(xiàng)。這些選項(xiàng)的目的是更好地模擬 Node.js 中 ECMAScript 模塊的精確查找規(guī)則; 然而,這種模式有許多其他工具沒(méi)有真正執(zhí)行的限制。

例如,在 Node.js 的 ECMAScript 模塊中,任何相對(duì)導(dǎo)入都需要包含文件擴(kuò)展名。

// entry.mjs
import * as utils from "./utils";     //  ? - 需要包括文件擴(kuò)展名。
import * as utils from "./utils.mjs"; //  ?

在Node.js和瀏覽器中這樣做是有原因的——它使文件查找更快,并且更適合原始文件服務(wù)器。但對(duì)于許多使用打包工具的開(kāi)發(fā)人員來(lái)說(shuō),node16/nodenext 的設(shè)置很麻煩,因?yàn)榇虬ぞ邲](méi)有這些限制中的大部分。在某些方面,node解析模式更適合使用打包工具的人。

但在某些方面,原有的 node 解析模式已經(jīng)過(guò)時(shí)了。 大多數(shù)現(xiàn)代打包工具在 Node.js 中使用 ECMAScript 模塊和 CommonJS 查找規(guī)則的融合。

為了模擬打包工具是如何工作的,TypeScript 5.0 引入了一個(gè)新策略:--moduleResolution bundler

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

如果正在使用現(xiàn)代打包工具,如 Vite、esbuild、swc、Webpack、Parcel 或其他實(shí)現(xiàn)混合查找策略的打包工具,那么新的 bundler 選項(xiàng)應(yīng)該非常適合你。

另一方面,如果正在編寫(xiě)一個(gè)打算在 npm 上發(fā)布的庫(kù),使用bundler選項(xiàng)可以隱藏不使用bundler的用戶(hù)可能出現(xiàn)的兼容性問(wèn)題。因此,在這些情況下,使用node16nodenext解析選項(xiàng)可能是更好的方法。

自定義解析標(biāo)志

JavaScript 工具現(xiàn)在可以模擬“混合”解析規(guī)則,就像上面描述的打包工具模式一樣。 由于工具的支持可能略有不同,TypeScript 5.0 提供了啟用或禁用一些功能的方法。

allowImportingTsExtensions

--allowImportingTsExtensions 允許 TypeScript 文件使用特定于 TypeScript 的擴(kuò)展名(如 .ts、.mts.tsx)相互導(dǎo)入。

僅當(dāng)啟用 --noEmit--emitDeclarationOnly 時(shí)才允許使用此標(biāo)志,因?yàn)檫@些導(dǎo)入路徑在運(yùn)行時(shí)無(wú)法在 JavaScript 輸出文件中解析。 這里的期望是解析器(例如打包工具、運(yùn)行時(shí)或其他工具)將使 .ts 文件之間的這些導(dǎo)入正常工作。

resolvePackageJsonExports

--resolvePackageJsonExports 強(qiáng)制 TypeScript 在從 node_modules 中的包中讀取時(shí)查詢(xún) package.json 文件的 exports 字段。

resolvePackageJsonImports

--resolvePackageJsonImports 強(qiáng)制 TypeScript 在從其祖先目錄包含 package.json 的文件執(zhí)行以 # 開(kāi)頭的查找時(shí)查詢(xún) package.json 文件的 imports 字段。

--moduleResolutionnode16、nodenextbundler 選項(xiàng)下,此選項(xiàng)默認(rèn)為 true。

allowArbitraryExtensions

在 TypeScript 5.0 中,當(dāng)導(dǎo)入路徑以不是已知 JavaScript 或 TypeScript 文件擴(kuò)展名的擴(kuò)展名結(jié)尾時(shí),編譯器將以 {file basename}.d.{extension} 的形式查找該路徑的聲明文件。例如,如果在打包項(xiàng)目中使用 CSS loader,可能希望為這些樣式表編寫(xiě)(或生成)聲明文件:

/* app.css */
.cookie-banner {
  display: none;
}
// app.d.css.ts
declare const css: {
  cookieBanner: string;
};
export default css;
// App.tsx
import styles from "./app.css";
styles.cookieBanner; // string

默認(rèn)情況下,這個(gè)導(dǎo)入將引發(fā)一個(gè)錯(cuò)誤,讓你知道TypeScript不理解這個(gè)文件類(lèi)型,你的運(yùn)行時(shí)可能不支持導(dǎo)入它。但是,如果已經(jīng)配置了運(yùn)行時(shí)或打包工具來(lái)處理它,則可以使用新--allowArbitraryExtensions編譯器選項(xiàng)來(lái)抑制錯(cuò)誤。

注意,可以通過(guò)添加一個(gè)名為 app.css.d.ts 而不是 app.d.css.ts 的聲明文件通??梢詫?shí)現(xiàn)類(lèi)似的效果。然而,這只是通過(guò) Node 對(duì) CommonJS 的 require 解析規(guī)則實(shí)現(xiàn)的。嚴(yán)格來(lái)說(shuō),前者被解釋為一個(gè)名為 app.css.js 的 JavaScript 文件的聲明文件。 因?yàn)橄嚓P(guān)文件導(dǎo)入需要在 Node 的 ESM 支持中包含擴(kuò)展名,所以在我們的例子中,TypeScript 會(huì)在 --moduleResolution node16 或 nodenext 下的 ESM 文件中出錯(cuò)。

customConditions

--customConditions 獲取當(dāng) TypeScript 從 package.json 的 [exports] 或 (nodejs.org/api/package…) 或 imports 字段解析時(shí)應(yīng)該成功的附加的條件列表。這些條件將添加到解析器默認(rèn)使用的現(xiàn)有條件中。

例如,當(dāng)此字段在 tsconfig.json 中設(shè)置為:

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

任何時(shí)候在 package.json 中引用 exports 或 imports 字段時(shí),TypeScript 都會(huì)考慮名為 my-condition 的條件。

因此,當(dāng)從具有以下 package.json 的包中導(dǎo)入時(shí):

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

TypeScript 將嘗試查找與foo.mjs對(duì)應(yīng)的文件。這個(gè)字段只有在 node16、nodenext 和--modulerresolution為 bundler 時(shí)才有效。

--verbatimModuleSyntax

默認(rèn)情況下,TypeScript 會(huì)執(zhí)行一些稱(chēng)為導(dǎo)入省略的操作。如果這樣寫(xiě):

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

TypeScript 檢測(cè)到只對(duì)類(lèi)型使用導(dǎo)入并完全刪除導(dǎo)入。輸出 JavaScript 可能是這樣的:

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

大多數(shù)時(shí)候這很好,因?yàn)槿绻?Car 不是從 ./car 導(dǎo)出的值,將得到一個(gè)運(yùn)行時(shí)錯(cuò)誤。但對(duì)于某些邊界情況,它確實(shí)增加了一層復(fù)雜性。例如,沒(méi)有像 import "./car" 這樣的語(yǔ)句,即完全放棄了 import,這實(shí)際上對(duì)有無(wú)副作用的模塊產(chǎn)生影響。

TypeScript 的 JavaScript emit 策略也有另外幾層復(fù)雜性——省略導(dǎo)入并不總是由如何使用 import 驅(qū)動(dòng)的,它通常還會(huì)參考值的聲明方式。所以并不總是很清楚是否像下面這樣的代碼:

export { Car } from "./car";

如果 Car 是用類(lèi)之類(lèi)的東西聲明的,那么它可以保存在生成的 JavaScript 文件中。 但是,如果 Car 僅聲明為類(lèi)型別名或接口,則 JavaScript 文件不應(yīng)導(dǎo)出 Car。

雖然 TypeScript 可能能夠根據(jù)來(lái)自跨文件的信息做出這些發(fā)出決策,但并非每個(gè)編譯器都可以。

imports 和 exports 的類(lèi)型修飾符在這些情況下會(huì)有幫助。我們可以明確指定importexport僅用于類(lèi)型分析,并且可以在JavaScript文件中使用類(lèi)型修飾符完全刪除。

// 這條語(yǔ)句可以在JS輸出中完全刪除
import type * as car from "./car";
// 在JS輸出中可以刪除命名的import/export Car
import { type Car } from "./car";
export { type Car } from "./car";

類(lèi)型修飾符本身并不是很有用——默認(rèn)情況下,模塊省略仍然會(huì)刪除導(dǎo)入,并且沒(méi)有強(qiáng)制區(qū)分類(lèi)型和普通導(dǎo)入和導(dǎo)出。 因此 TypeScript 有標(biāo)志 --importsNotUsedAsValues 以確保使用 type 修飾符,--preserveValueImports 以防止某些模塊省略行為,以及 --isolatedModules 以確保 TypeScript 代碼適用于不同的編譯器。 不幸的是,很難理解這 3 個(gè)標(biāo)志的細(xì)節(jié),并且仍然存在一些具有意外行為的邊界情況。

TypeScript 5.0 引入了一個(gè)名為 --verbatimModuleSyntax 的新選項(xiàng)來(lái)簡(jiǎn)化這種情況。規(guī)則要簡(jiǎn)單得多,任何沒(méi)有 type 修飾符的導(dǎo)入或?qū)С龆紩?huì)被保留。任何使用 type 修飾符的內(nèi)容都會(huì)被完全刪除。

// 完全被刪除
import type { A } from "a";
// 重寫(xiě)為 'import { b } from "bcd";'
import { b, type c, type d } from "bcd";
// 重寫(xiě)為 'import {} from "xyz";'
import { type xyz } from "xyz";

有了這個(gè)新選項(xiàng),所見(jiàn)即所得。不過(guò),當(dāng)涉及到模塊互操作時(shí),這確實(shí)有一些影響。 在此標(biāo)志下,當(dāng)設(shè)置或文件擴(kuò)展名暗示不同的模塊系統(tǒng)時(shí),ECMAScript 導(dǎo)入和導(dǎo)出不會(huì)被重寫(xiě)為 require 調(diào)用。相反,會(huì)得到一個(gè)錯(cuò)誤。 如果需要生成使用 requiremodule.exports 的代碼,則必須使用早于 ES2015 的 TypeScript 模塊語(yǔ)法:

雖然這是一個(gè)限制,但它確實(shí)有助于使一些問(wèn)題更加明顯。 例如,忘記在 --module node16 下的 package.json 中設(shè)置 type 字段是很常見(jiàn)的。 因此,開(kāi)發(fā)人員會(huì)在沒(méi)有意識(shí)到的情況下開(kāi)始編寫(xiě) CommonJS 模塊而不是 ES 模塊,從而給出意外的查找規(guī)則和 JavaScript 輸出。 這個(gè)新標(biāo)志確保有意使用正在使用的文件類(lèi)型,因?yàn)檎Z(yǔ)法是有意不同的。

因?yàn)?--verbatimModuleSyntax 提供了比 --importsNotUsedAsValues--preserveValueImports 更一致的作用,所以這兩個(gè)現(xiàn)有標(biāo)志被棄用了。

支持 export type *

當(dāng) TypeScript 3.8 引入僅類(lèi)型導(dǎo)入時(shí),新語(yǔ)法不允許在 export * from "module" 或 export * as ns from "module" 重新導(dǎo)出時(shí)使用。 TypeScript 5.0 添加了對(duì)這兩種形式的支持:

// models/vehicles.ts
export class Spaceship {
  // ...
}
// models/index.ts
export type * as vehicles from "./vehicles";
// main.ts
import { vehicles } from "./models";
function takeASpaceship(s: vehicles.Spaceship) {
  //  ?
}
function makeASpaceship() {
  return new vehicles.Spaceship();
  //         ^^^^^^^^
  // vehicles 不能用作值,因?yàn)樗鞘褂谩癳xport type”導(dǎo)出的。
}

JSDoc 支持 @satisfies

TypeScript 4.9 引入了 satisfies 操作符。它確保表達(dá)式的類(lèi)型是兼容的,而不影響類(lèi)型本身。以下面的代碼為例:

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 是用數(shù)組聲明的,因?yàn)殡m然 satisfies 驗(yàn)證了對(duì)象的類(lèi)型,但它并沒(méi)有直接將其更改為 CompilerOptions 而丟失信息。所以如果想映射到 extends 上,是可以的。

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

這對(duì) TypeScript 用戶(hù)很有幫助,但是很多人使用 TypeScript 來(lái)使用 JSDoc 注釋對(duì) JavaScript 代碼進(jìn)行類(lèi)型檢查。 這就是為什么 TypeScript 5.0 支持一個(gè)名為 @satisfies 的新 JSDoc 標(biāo)簽,它做的事情完全一樣。

/** @satisfies */ 可以捕獲類(lèi)型不匹配:

// @ts-check
/**
 * @typedef CompilerOptions
 * @prop {boolean} [strict]
 * @prop {string} [outDir]
 */
/**
 * @satisfies {CompilerOptions}
 */
let myCompilerOptions = {
    outdir: "../lib",
//  ~~~~~~ oops! we meant outDir
};

但它會(huì)保留表達(dá)式的原始類(lèi)型,允許稍后在代碼中更精確地使用值。

// @ts-check
/**
 * @typedef CompilerOptions
 * @prop {boolean} [strict]
 * @prop {string} [outDir]
 */
/**
 * @typedef ConfigSettings
 * @prop {CompilerOptions} [compilerOptions]
 * @prop {string | string[]} [extends]
 */
/**
 * @satisfies {ConfigSettings}
 */
let myConfigSettings = {
    compilerOptions: {
        strict: true,
        outDir: "../lib",
    },
    extends: [
        "@tsconfig/strictest/tsconfig.json",
        "../../../tsconfig.base.json"
    ],
};
let inheritedConfigs = myConfigSettings.extends.map(resolveConfig);

/** @satisfies */ 也可以?xún)?nèi)嵌在任何帶括號(hào)的表達(dá)式上。 可以這樣寫(xiě) myCompilerOptions

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

這可能在函數(shù)調(diào)用時(shí)更有意義:

compileCode(/** @satisfies {CompilerOptions} */ ({
    // ...
}));

JSDoc 支持 @overload

在 TypeScript 中,可以為函數(shù)指定重載。 重載提供了一種方式,用不同的參數(shù)調(diào)用一個(gè)函數(shù),并返回不同的結(jié)果。它可以限制調(diào)用者實(shí)際使用函數(shù)的方式,并優(yōu)化將返回的結(jié)果。

// 重載:
function printValue(str: string): void;
function printValue(num: number, maxFractionDigits?: number): void;
// 實(shí)現(xiàn):
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 將字符串或數(shù)字作為第一個(gè)參數(shù)。如果它需要一個(gè)數(shù)字,它可以使用第二個(gè)參數(shù)來(lái)確定可以打印多少個(gè)小數(shù)位。

TypeScript 5.0 現(xiàn)在允許 JSDoc 使用新的 @overload 標(biāo)簽聲明重載。 每個(gè)帶有 @overload標(biāo)簽的 JSDoc 注釋都被視為以下函數(shù)聲明的不同重載。

// @ts-check
/**
 * @overload
 * @param {string} value
 * @return {void}
 */
/**
 * @overload
 * @param {number} value
 * @param {number} [maximumFractionDigits]
 * @return {void}
 */
/**
 * @param {string | number} value
 * @param {number} [maximumFractionDigits]
 */
function printValue(value, maximumFractionDigits) {
    if (typeof value === "number") {
        const formatter = Intl.NumberFormat("en-US", {
            maximumFractionDigits,
        });
        value = formatter.format(value);
    }
    console.log(value);
}

現(xiàn)在,無(wú)論是在 TypeScript 還是 JavaScript 文件中編寫(xiě),TypeScript 都可以讓我們知道是否錯(cuò)誤地調(diào)用了函數(shù)。

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

編輯器中不區(qū)分大小寫(xiě)的導(dǎo)入排序

在 Visual Studio 和 VS Code 等編輯器中,TypeScript 支持組織和排序?qū)牒蛯?dǎo)出的體驗(yàn)。 但是,對(duì)于列表何時(shí)“排序”,通常會(huì)有不同的解釋。

例如,下面的導(dǎo)入列表是否排序?

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

答案可能是“視情況而定”。 如果不關(guān)心區(qū)分大小寫(xiě),那么這個(gè)列表顯然沒(méi)有排序。 字母 f 出現(xiàn)在 t 和 T 之前。

但在大多數(shù)編程語(yǔ)言中,排序默認(rèn)是比較字符串的字節(jié)值。JavaScript 比較字符串的方式意味著“Toggle”總是在“freeze”之前,因?yàn)楦鶕?jù) ASCII 字符編碼,大寫(xiě)字母在小寫(xiě)字母之前。 所以從這個(gè)角度來(lái)看,導(dǎo)入列表是已排序的。

TypeScript 之前認(rèn)為導(dǎo)入列表是已排序的,因?yàn)樗鼤?huì)做基本的區(qū)分大小寫(xiě)的排序。 對(duì)于喜歡不區(qū)分大小寫(xiě)排序的開(kāi)發(fā)人員,或者使用像 ESLint 這樣默認(rèn)需要不區(qū)分大小寫(xiě)排序的工具的開(kāi)發(fā)人員來(lái)說(shuō),這可能是一個(gè)阻礙。

TypeScript 現(xiàn)在默認(rèn)檢測(cè)大小寫(xiě)。這意味著 TypeScript 和 ESLint 等工具通常不會(huì)就如何最好地對(duì)導(dǎo)入進(jìn)行排序而相互“斗爭(zhēng)”。

這些選項(xiàng)最終可能由編輯器配置。目前,它們?nèi)匀徊环€(wěn)定且處于試驗(yàn)階段,現(xiàn)在可以通過(guò)在 JSON 選項(xiàng)中使用 typescript.unstable 在 VS Code 中選擇加入它們。 以下是可以嘗試的所有選項(xiàng)(設(shè)置為默認(rèn)值):

{
    "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

在編寫(xiě) switch 語(yǔ)句時(shí),TypeScript 現(xiàn)在會(huì)檢測(cè)被檢查的值何時(shí)具有字面量類(lèi)型。以提供更便利的代碼快捷輸入:

速度、內(nèi)存和包大小優(yōu)化

TypeScript 5.0 在代碼結(jié)構(gòu)、數(shù)據(jù)結(jié)構(gòu)和算法實(shí)現(xiàn)中包含許多強(qiáng)大的變化。這些都意味著整個(gè)體驗(yàn)應(yīng)該更快——不僅僅是運(yùn)行 TypeScript,甚至安裝它。

以下是相對(duì)于 TypeScript 4.9 在速度和大小方面的優(yōu)勢(shì):

場(chǎng)景時(shí)間或大小相對(duì)于 TS 4.9
material-ui 構(gòu)建時(shí)間90%
TypeScript 編譯器啟動(dòng)時(shí)間89%
Playwright 構(gòu)建時(shí)間88%
TypeScript 編譯器自構(gòu)建時(shí)間87%
Outlook Web 構(gòu)建時(shí)間82%
VS Code 構(gòu)建時(shí)間80%
TypeScript npm 包大小59%

圖表形式:

TypeScript 包大小變化:

那為什么會(huì)有如此大的提升呢?部分優(yōu)化細(xì)節(jié)如下:

首先,將 TypeScript 從命名空間遷移到模塊,這樣就能夠利用現(xiàn)代構(gòu)建工具來(lái)執(zhí)行優(yōu)化。重新審視了打包策略并刪除一些已棄用的代碼,已將 TypeScript 4.9 的 63.8 MB 包大小減少了約 26.4 MB。還通過(guò)直接函數(shù)調(diào)用帶來(lái)了顯著的速度提升。

在將信息序列化為字符串時(shí),執(zhí)行了一些緩存。 類(lèi)型顯示可能作為錯(cuò)誤報(bào)告、聲明觸發(fā)、代碼補(bǔ)全等的一部分發(fā)生,最終可能會(huì)相當(dāng)昂貴。TypeScript 現(xiàn)在緩存了一些常用的機(jī)制以在這些操作中重用。

總的來(lái)說(shuō),預(yù)計(jì)大多數(shù)代碼庫(kù)應(yīng)該會(huì)看到 TypeScript 5.0 的速度提升,并且始終能夠重現(xiàn) 10% 到 20% 之間的提升。當(dāng)然,這將取決于硬件和代碼庫(kù)特性。

其他重大更改和棄用

運(yùn)行時(shí)要求

TypeScript 現(xiàn)在的 target 是 ECMAScript 2018。TypeScript 軟件包還將預(yù)期的最低引擎版本設(shè)置為 12.20。對(duì)于 Node.js 用戶(hù)來(lái)說(shuō),這意味著 TypeScript 5.0 需要至少Node.js 12.20 或更高版本才能運(yùn)行。

lib.d.ts 變化

更改 DOM 類(lèi)型的生成方式可能會(huì)對(duì)現(xiàn)有代碼產(chǎn)生影響。注意,某些屬性已從數(shù)字轉(zhuǎn)換為數(shù)字字面量類(lèi)型,并且用于剪切、復(fù)制和粘貼事件處理的屬性和方法已跨接口移動(dòng)。

API 重大變更

在 TypeScript 5.0 中, 轉(zhuǎn)向了模塊,刪除了一些不必要的接口,并進(jìn)行了一些正確性改進(jìn)。

關(guān)系運(yùn)算符中的禁止隱式強(qiáng)制

如果編寫(xiě)的代碼可能導(dǎo)致隱式字符串到數(shù)字的強(qiáng)制轉(zhuǎn)換,TypeScript 中的某些操作現(xiàn)在會(huì)進(jìn)行警告:

function func(ns: number | string) {
  return ns * 4; // 錯(cuò)誤,可能存在隱式強(qiáng)制轉(zhuǎn)換
}

在 5.0 中,這也將應(yīng)用于關(guān)系運(yùn)算符 >、<、<= 和 >=:

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

如果需要這樣做,可以使用+顯式地將操作數(shù)轉(zhuǎn)換為數(shù)字:

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

棄用和默認(rèn)更改

在 TypeScript 5.0 中,棄用了以下設(shè)置和設(shè)置值:

  • --target: ES3
  • --out
  • --noImplicitUseStrict
  • --keyofStringsOnly
  • --suppressExcessPropertyErrors
  • --suppressImplicitAnyIndexErrors
  • --noStrictGenericChecks
  • --charset
  • --importsNotUsedAsValues
  • --preserveValueImports

在 TypeScript 5.5 之前,這些配置將繼續(xù)被允許使用,屆時(shí)它們將被完全刪除,但是,如果正在使用這些設(shè)置,將收到警告。 在 TypeScript 5.0 以及未來(lái)版本 5.1、5.2、5.3 和 5.4 中,可以指定 "ignoreDeprecations": "5.0" 以消除這些警告。 很快會(huì)發(fā)布一個(gè) 4.9 補(bǔ)丁,允許指定 ignoreDeprecations 以實(shí)現(xiàn)更平滑的升級(jí)。除了棄用之外,還更改了一些設(shè)置以更好地改進(jìn) TypeScript 中的跨平臺(tái)行為。

  • --newLine,控制 JavaScript 文件中發(fā)出的行結(jié)束符,如果沒(méi)有指定,過(guò)去是根據(jù)當(dāng)前操作系統(tǒng)推斷的。我們認(rèn)為構(gòu)建應(yīng)該盡可能確定,Windows 記事本現(xiàn)在支持換行符,所以新的默認(rèn)設(shè)置是 LF。 舊的特定于操作系統(tǒng)的推理行為不再可用。
  • --forceConsistentCasingInFileNames,它確保項(xiàng)目中對(duì)相同文件名的所有引用都在大小寫(xiě)中達(dá)成一致,現(xiàn)在默認(rèn)為 true。 這有助于捕獲在不區(qū)分大小寫(xiě)的文件系統(tǒng)上編寫(xiě)的代碼的差異問(wèn)題。

參考資料

以上就是TypeScript 5.0 正式發(fā)布使用指南的詳細(xì)內(nèi)容,更多關(guān)于TypeScript 5.0使用指南的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論