最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

TypeScript 5.0 正式發(fā)布!

2023-03-17 15:57 作者:要寵你上天  | 我要投稿

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

可以通過以下 npm 命令開始使用 TypeScript 5.0:

npm?install?-D?typescript

以下是 TypeScript 5.0 的主要更新:

  • 全新裝飾器

  • const?類型參數(shù)

  • extends?支持多配置文件

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

  • --moduleResolutionbundler

  • 自定義解析標志

  • --verbatimModuleSyntax

  • 支持?export type *

  • JSDoc 支持?@satisfies

  • JSDoc 支持?@overload

  • 編輯器中不區(qū)分大小寫的導入排序

  • 完善?switch/case

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

  • 其他重大更改和棄用

全新裝飾器

裝飾器是即將推出的 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?方法很簡單,在實際中它內(nèi)部可能會跟復雜,比如需要執(zhí)行異步邏輯,或者進行遞歸,亦或是有副作用等。那就可能需要使用?console.log?來調(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.")
????}
}

如果有一種方法可以為每種方法做到這一點,可能會很好。

這就是裝飾器的用武之地。我們可以編寫一個名為?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,可以暫時忽略,這樣可以讓例子盡可能得簡單。

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

  1. 打?。篖OG: Entering method.

  2. 將 this 及其所有參數(shù)傳遞給原始方法

  3. 打印:LOG: Exiting method.

  4. 返回原始方法的執(zhí)行結(jié)果

現(xiàn)在我們就可以使用?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?對象調(diào)用。因為?loggedMethod?返回了一個新函數(shù),該函數(shù)替換了?greet?的原始定義。

loggedMethod 的第二個參數(shù)被稱為“?context?對象”,它包含一些關(guān)于如何聲明裝飾方法的有用信息——比如它是?#private?成員還是靜態(tài)成員,或者方法的名稱是什么。 下面來重寫 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?對象進行建模。除了元數(shù)據(jù)之外,方法的?context?對象還有一個有用的函數(shù):addInitializer。 這是一種掛接到構(gòu)造函數(shù)開頭的方法(如果使用靜態(tài)方法,則掛接到類本身的初始化)。

舉個例子,在JavaScript中,經(jīng)常會寫如下的模式:

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}.`);
????};
}

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

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

greet();

可以編寫一個裝飾器,使用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不會返回任何內(nèi)容,所以當它裝飾一個方法時,它會保留原來的方法。相反,它會在其他字段初始化之前添加邏輯。

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的結(jié)果。在這個例子中,這沒有關(guān)系——但如果裝飾器有副作用或期望某種順序,則可能有關(guān)系。

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

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

我們甚至可以創(chuàng)建返回裝飾器函數(shù)的函數(shù)。這使得我們可以對最終的裝飾器進行一些自定義。如果我們愿意,我們可以讓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作為裝飾器之前調(diào)用它。然后,可以傳入任何字符串作為記錄到控制臺的消息的前綴。

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'.

裝飾器可不僅僅用于方法,還可以用于屬性/字段、gettersetter和自動訪問器。甚至類本身也可以裝飾成子類化和注冊。

上面的loggedMethodbound裝飾器示例寫的很簡單,并省略了大量關(guān)于類型的細節(jié)。實際上,編寫裝飾器可能相當復雜。例如,上面的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類型參數(shù)分別建模this、參數(shù)和原始方法的返回類型。

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

const 類型參數(shù)

當推斷一個對象的類型時,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"]});

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

//?我們想要的類型:?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 中,可以在類型參數(shù)聲明中添加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?const
const?names?=?getNamesExactly({?names:?["Alice",?"Bob",?"Eve"]?});

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

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數(shù)組不能用于需要可變數(shù)組的地方。在這種情況下,推理回退到約束,數(shù)組被視為string[],調(diào)用仍然成功進行。

更好的定義應該使用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)用中編寫的對象、數(shù)組和基本類型表達式的推斷,所以不會(或不能)用const修飾的參數(shù)將看不到任何行為的變化:

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

//??T?仍然是?string[],const?修飾符沒有作用
fnGood(arr);

extends 支持多配置文件

當管理多個項目時,通常每個項目的?tsconfig.json?文件都會繼承于基礎(chǔ)配置。這就是為什么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,就必須手動禁用這些選項,或者創(chuàng)建一個不從?@tsconfig/strictest?擴展的單獨版本的?tsconfig.base.json。

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

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

這樣寫有點像直接擴展 c,其中 c 擴展 b,b 擴展 a。 如果任何字段“沖突”,則后一個項生效。

所以在下面的例子中,strictNullChecks?和?noImplicitAny?都會在最終的?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",
????????//?...
????}
}

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

當 TypeScript 最初引入枚舉時,它只不過是一組具有相同類型的數(shù)值常量:

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

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

function?takeValue(e:?E)?{}

takeValue(E.Foo);?//??
takeValue(123);???//??

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

//?Color就像是一個聯(lián)合:Red?|?Orange?|?Yellow?|?Green?|?Blue?|?Violet
enum?Color?{
????Red,?Orange,?Yellow,?Green,?Blue,?/*?Indigo?*/,?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;
}

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

enum?E?{
????Blah?=?Math.random()
}

每當TypeScript遇到這些問題時,它都會悄無聲息地退出并使用舊的枚舉策略。這意味著要放棄并集和字面量類型的所有優(yōu)點。

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

--moduleResolution

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

例如,在 Node.js 的 ECMAScript 模塊中,任何相對導入都需要包含文件擴展名。

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

import?*?as?utils?from?"./utils.mjs";?//???

在Node.js和瀏覽器中這樣做是有原因的——它使文件查找更快,并且更適合原始文件服務器。但對于許多使用打包工具的開發(fā)人員來說,node16/nodenext 的設置很麻煩,因為打包工具沒有這些限制中的大部分。在某些方面,node解析模式更適合使用打包工具的人。

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

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

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

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

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

自定義解析標志

JavaScript 工具現(xiàn)在可以模擬“混合”解析規(guī)則,就像上面描述的打包工具模式一樣。 由于工具的支持可能略有不同,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?的文件執(zhí)行以?#?開頭的查找時查詢?package.json?文件的?imports?字段。

在?--moduleResolution?的?node16、nodenext?和?bundler?選項下,此選項默認為 true。

allowArbitraryExtensions

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

/*?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

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

注意,可以通過添加一個名為?app.css.d.ts?而不是?app.d.css.ts?的聲明文件通??梢詫崿F(xiàn)類似的效果。然而,這只是通過 Node 對 CommonJS 的 require 解析規(guī)則實現(xiàn)的。嚴格來說,前者被解釋為一個名為?app.css.js?的 JavaScript 文件的聲明文件。 因為相關(guān)文件導入需要在 Node 的 ESM 支持中包含擴展名,所以在我們的例子中,TypeScript 會在?--moduleResolution?node16 或 nodenext 下的 ESM 文件中出錯。

customConditions

--customConditions?獲取當 TypeScript 從?package.json?的 [exports] 或 ([nodejs.org/api/packages.html#exports]) 或?imports?字段解析時應該成功的附加的條件列表。這些條件將添加到解析器默認使用的現(xiàn)有條件中。

例如,當此字段在 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 會執(zhí)行一些稱為導入省略的操作。如果這樣寫:

import?{?Car?}?from?"./car";

export?function?drive(car:?Car)?{
????//?...
}

TypeScript 檢測到只對類型使用導入并完全刪除導入。輸出 JavaScript 可能是這樣的:

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

大多數(shù)時候這很好,因為如果?Car?不是從?./car?導出的值,將得到一個運行時錯誤。但對于某些邊界情況,它確實增加了一層復雜性。例如,沒有像?import "./car"?這樣的語句,即完全放棄了?import,這實際上對有無副作用的模塊產(chǎn)生影響。

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

export?{?Car?}?from?"./car";

如果 Car 是用類之類的東西聲明的,那么它可以保存在生成的 JavaScript 文件中。 但是,如果 Car 僅聲明為類型別名或接口,則 JavaScript 文件不應導出 Car。

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

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

//?這條語句可以在JS輸出中完全刪除
import?type?*?as?car?from?"./car";

//?在JS輸出中可以刪除命名的import/export?Car
import?{?type?Car?}?from?"./car";
export?{?type?Car?}?from?"./car";

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

TypeScript 5.0 引入了一個名為?--verbatimModuleSyntax?的新選項來簡化這種情況。規(guī)則要簡單得多,任何沒有?type?修飾符的導入或?qū)С龆紩槐A?。任何使?type?修飾符的內(nèi)容都會被完全刪除。

//?完全被刪除
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";

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

雖然這是一個限制,但它確實有助于使一些問題更加明顯。 例如,忘記在 --module node16 下的?package.json?中設置?type?字段是很常見的。 因此,開發(fā)人員會在沒有意識到的情況下開始編寫 CommonJS 模塊而不是 ES 模塊,從而給出意外的查找規(guī)則和 JavaScript 輸出。 這個新標志確保有意使用正在使用的文件類型,因為語法是有意不同的。

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

支持 export type *

當 TypeScript 3.8 引入僅類型導入時,新語法不允許在 export * from "module" 或 export * as ns from "module" 重新導出時使用。 TypeScript 5.0 添加了對這兩種形式的支持:

//?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?不能用作值,因為它是使用“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?是用數(shù)組聲明的,因為雖然?satisfies?驗證了對象的類型,但它并沒有直接將其更改為?CompilerOptions?而丟失信息。所以如果想映射到?extends?上,是可以的。

declare?function?resolveConfig(configPath:?string):?CompilerOptions;

let?inheritedConfigs?=?myConfigSettings.extends.map(resolveConfig);

這對 TypeScript 用戶很有幫助,但是很多人使用 TypeScript 來使用 JSDoc 注釋對 JavaScript 代碼進行類型檢查。 這就是為什么 TypeScript 5.0 支持一個名為?@satisfies?的新 JSDoc 標簽,它做的事情完全一樣。

/** @satisfies */?可以捕獲類型不匹配:

//?@ts-check

/**
?*?@typedef?CompilerOptions
?*?@prop?{boolean}?[strict]
?*?@prop?{string}?[outDir]
?*/

/**
?*?@satisfies?{CompilerOptions}
?*/
let?myCompilerOptions?=?{
????outdir:?"../lib",
//??~~~~~~?oops!?we?meant?outDir
};

但它會保留表達式的原始類型,允許稍后在代碼中更精確地使用值。

//?@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 */?也可以內(nèi)嵌在任何帶括號的表達式上。 可以這樣寫?myCompilerOptions

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

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

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

JSDoc 支持?@overload

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

//?重載:
function?printValue(str:?string):?void;
function?printValue(num:?number,?maxFractionDigits?:?number):?void;

//?實現(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ù)字作為第一個參數(shù)。如果它需要一個數(shù)字,它可以使用第二個參數(shù)來確定可以打印多少個小數(shù)位。

TypeScript 5.0 現(xiàn)在允許 JSDoc 使用新的?@overload?標簽聲明重載。 每個帶有?@overload標簽的 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)在,無論是在 TypeScript 還是 JavaScript 文件中編寫,TypeScript 都可以讓我們知道是否錯誤地調(diào)用了函數(shù)。

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

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

編輯器中不區(qū)分大小寫的導入排序

在 Visual Studio 和 VS Code 等編輯器中,TypeScript 支持組織和排序?qū)牒蛯С龅捏w驗。 但是,對于列表何時“排序”,通常會有不同的解釋。

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

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

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

但在大多數(shù)編程語言中,排序默認是比較字符串的字節(jié)值。JavaScript 比較字符串的方式意味著“Toggle”總是在“freeze”之前,因為根據(jù) ASCII 字符編碼,大寫字母在小寫字母之前。 所以從這個角度來看,導入列表是已排序的。

TypeScript 之前認為導入列表是已排序的,因為它會做基本的區(qū)分大小寫的排序。 對于喜歡不區(qū)分大小寫排序的開發(fā)人員,或者使用像 ESLint 這樣默認需要不區(qū)分大小寫排序的工具的開發(fā)人員來說,這可能是一個阻礙。

TypeScript 現(xiàn)在默認檢測大小寫。這意味著 TypeScript 和 ESLint 等工具通常不會就如何最好地對導入進行排序而相互“斗爭”。

這些選項最終可能由編輯器配置。目前,它們?nèi)匀徊环€(wěn)定且處于試驗階段,現(xiàn)在可以通過在 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 現(xiàn)在會檢測被檢查的值何時具有字面量類型。以提供更便利的代碼快捷輸入:?

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

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

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

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

圖表形式:

TypeScript 包大小變化:

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

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

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

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

其他重大更改和棄用

運行時要求

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

lib.d.ts 變化

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

API 重大變更

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

關(guān)系運算符中的禁止隱式強制

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

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

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

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

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

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

棄用和默認更改

在 TypeScript 5.0 中,棄用了以下設置和設置值:

  • --target: ES3

  • --out

  • --noImplicitUseStrict

  • --keyofStringsOnly

  • --suppressExcessPropertyErrors

  • --suppressImplicitAnyIndexErrors

  • --noStrictGenericChecks

  • --charset

  • --importsNotUsedAsValues

  • --preserveValueImports

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

  • --newLine,控制 JavaScript 文件中發(fā)出的行結(jié)束符,如果沒有指定,過去是根據(jù)當前操作系統(tǒng)推斷的。我們認為構(gòu)建應該盡可能確定,Windows 記事本現(xiàn)在支持換行符,所以新的默認設置是 LF。 舊的特定于操作系統(tǒng)的推理行為不再可用。

  • --forceConsistentCasingInFileNames,它確保項目中對相同文件名的所有引用都在大小寫中達成一致,現(xiàn)在默認為?true。 這有助于捕獲在不區(qū)分大小寫的文件系統(tǒng)上編寫的代碼的差異問題。


TypeScript 5.0 正式發(fā)布!的評論 (共 條)

分享到微博請遵守國家法律
株洲市| 德化县| 琼海市| 兴隆县| 新密市| 浏阳市| 芮城县| 理塘县| 土默特右旗| 珠海市| 观塘区| 兰溪市| 安龙县| 历史| 进贤县| 镇赉县| 岳阳市| 石阡县| 郎溪县| 江城| 柘城县| 兴仁县| 永泰县| 汪清县| 靖安县| 吐鲁番市| 高雄市| 临安市| 阳曲县| 黄浦区| 科尔| 建湖县| 西乌| 海伦市| 临洮县| 浦县| 延长县| 巩义市| 新龙县| 南康市| 象山县|