TypeScript 5.0 已發(fā)布!看看增加了什么新功能
本文作者是螞蟻集團(tuán)前端工程師厚發(fā),基于《TypeScript 5.0 Beta 發(fā)布》,結(jié)合最新的發(fā)布說明內(nèi)容,重新修改的。TypeScript 正式迎來 5.0 時(shí)代。
自 Beta 版本以來,有幾個(gè)顯著的更改
其中一個(gè)新變化是 TypeScript 允許在 export 和 export default 之前或之后放置裝飾器。這一變化反映了 TC39(ECMAScript/JavaScript 的標(biāo)準(zhǔn)組織)內(nèi)的討論和共識(shí)。
另一個(gè)變化是,新的模塊解析選項(xiàng)(moduleResolution)“bundler”?現(xiàn)在只能在將?--module
?選項(xiàng)設(shè)置為?esnext
?時(shí)使用。這是為了確保在 bundler 解析?import
?語句之前,不管 bundler 或加載器(loader)是否使用?TypeScript 的模塊選項(xiàng),輸入文件中編寫的?import
?語句都不會(huì)轉(zhuǎn)換為?require
?調(diào)用。在這些發(fā)行說明中,我們也提供了一些上下文信息,建議大多數(shù)庫作者使用?node16
?或?nodenext
。
盡管 TypeScript 5.0 Beta 版本中已經(jīng)具備了此功能,但我們沒有為支持編輯器場(chǎng)景中不區(qū)分大小寫的導(dǎo)入排序編寫文檔。這在一定程度上是因?yàn)樽远x UX 仍在討論中,但是默認(rèn)情況下,TypeScript 現(xiàn)在應(yīng)該與您的其他工具更好地配合使用。具體介紹在后面。
自我們發(fā)布 RC 版本以來,最顯著的變化是 TypeScript 5.0 現(xiàn)在在 package.json 中指定了 Node.js 的最低版本為 12.20。我們還發(fā)布了一篇有關(guān) TypeScript 5.0 遷移到模塊的文章,并鏈接到了它。(https://devblogs.microsoft.com/typescript/typescripts-migration-to-modules/)
自 TypeScript 5.0 Beta 和 RC 發(fā)布以來,速度基準(zhǔn)測(cè)試和包大小差異的具體數(shù)字也已經(jīng)進(jìn)行了調(diào)整,盡管噪聲是運(yùn)行的一個(gè)因素。一些基準(zhǔn)測(cè)試的名稱也已經(jīng)進(jìn)行了調(diào)整以提高清晰度,并且包大小的改進(jìn)已經(jīng)移動(dòng)到一個(gè)單獨(dú)的圖表中。
BreakChanges And Deprecations
運(yùn)行時(shí)要求
要求 Node.js 10.x 以上。
lib.d.ts變更
例行環(huán)節(jié)。具體變更在這:lib.d.ts change (https://github.com/microsoft/TypeScript/pull/52328/files)
后面實(shí)際項(xiàng)目中如果有遇到一些常用的用法報(bào)錯(cuò)了,需要手動(dòng)更改代碼的 case,筆者會(huì)在這里補(bǔ)充,目前還沒發(fā)現(xiàn)。
關(guān)系運(yùn)算符中禁止隱式類型轉(zhuǎn)換
5.0 之前 TypeScript 只會(huì)檢查?+
?-
?*
?/
運(yùn)算符的隱式類型轉(zhuǎn)換,提示類型報(bào)錯(cuò)。
5.0 之后關(guān)系運(yùn)算符?>
?<
?<=
?>=
也檢查。
經(jīng)典操作,可以通過?+
?運(yùn)算符來進(jìn)行進(jìn)行轉(zhuǎn)換。
enum 類型檢修
自從 TypeScript 支持 enum 枚舉類型以來,一直都有些長期存在的奇怪的問題。官方說在 5.0,他們正在處理解決這些問題。
給 enum 類型變量賦值字面量時(shí),如果超出了 enum 定義范圍,會(huì)報(bào)錯(cuò)
注意是字面量。下面這種情況還是不會(huì)報(bào)錯(cuò):
修復(fù)之前的一個(gè)問題:由多個(gè) enum 類型組成的混合 enum 類型,enum 的成員都會(huì)是 number 類型
官方的例子:
修飾器
ECMASCript 的修飾器標(biāo)準(zhǔn)已經(jīng)進(jìn)到了 Stage3 階段了,TypeScript 5.0 落地了 ECMASCript 的修飾器標(biāo)準(zhǔn)。修飾器簡單來說是一個(gè)函數(shù),可以用于修飾類、類的成員方法/屬性。
修飾器使用說明
官方以一個(gè)類方法修飾器來舉了個(gè)例子:
代碼中,loggedMethod
就是一個(gè)類方法修飾器。它是一個(gè)函數(shù),函數(shù)的第一個(gè)入?yún)⑹潜恍揎椀?code>greeet方法的初始值,第二個(gè)入?yún)?code>context是上下文信息。修飾器最后返回的是replacementMethod
函數(shù),當(dāng)被修飾的方法greet
被調(diào)用時(shí),執(zhí)行的就是replacementMethod
函數(shù)。我們可以把一些可復(fù)用的代碼邏輯放到loggedMethod
修飾器中,那就可以基于修飾器去復(fù)用代碼了。
修飾器上下文
非常值得關(guān)注的是,第二個(gè)入?yún)?code>context提供了一個(gè)上下文信息對(duì)象。根據(jù)修飾器的種類不同有不同的類型:類修飾器:ClassDecoratorContext
、類方法修飾器:ClassMethodDecoratorContext
、類屬性getter修飾器:ClassGetterDecoratorContext
、類屬性setter修飾器:ClassSetterDecoratorContext
、類屬性修飾器:ClassFieldDecoratorContext
、類accessor修飾器:ClassAccessorDecoratorContext
context
的類型定義大致是這樣的:
addInitializer(新語法)
上下文中的addInitializer
方法是標(biāo)準(zhǔn)中的新語法。官方文檔解釋:
It’s a way to hook into the beginning of the constructor (or the initialization of the class itself if we’re working with statics)
當(dāng)修飾器用來修飾非 static 屬性/方法時(shí),可以通過這個(gè)方法在實(shí)例初始化時(shí),構(gòu)造函數(shù)執(zhí)行之前指定執(zhí)行邏輯。當(dāng)修飾器用來修飾 static 屬性/方法時(shí),可以通過這個(gè)方法類初始化時(shí),指定執(zhí)行邏輯。舉個(gè)例子就清晰很多:
Initializer?
方法執(zhí)行時(shí)機(jī)
類的靜態(tài)屬性/方法初始化,在類初始化過程中。
staticMethodDecorator
、staticFiledDecorator
中通過addInitializer
方法增加的初始化函數(shù),先執(zhí)行。
classFiledDecorator
中通過addInitializer
方法增加的初始化函數(shù),在類初始化之后執(zhí)行。類進(jìn)行實(shí)例化? ? ? ? ? ? ? ?? ? ? ? ? ? ? ??
instanceMethod
、instanceFiledDecorator
中通過addInitializer
方法增加的初始化函數(shù),在實(shí)例初始化時(shí),構(gòu)造函數(shù)執(zhí)行之前執(zhí)行。
Initializer?
方法的應(yīng)用
官網(wǎng)例子:使用addInitializer()
綁定this
。
與之前版本實(shí)驗(yàn)性的修飾器的不同
之前版本的 TypeScript 也支持修飾器(https://typescript.bootcss.com/decorators.html),需要增加--experimentalDecorators
編譯選項(xiàng)。
TypeScript 5.0 的修飾器標(biāo)準(zhǔn)跟之前的修飾器是不兼容的。舊版的?--experimentalDecorators?
選項(xiàng)將會(huì)仍然保留,如果啟用此配置,則仍然會(huì)將裝飾器視為舊版,新版的裝飾器無需任何配置就能夠默認(rèn)啟用。
TypeScript5.0 的修飾器標(biāo)準(zhǔn)跟之前的元數(shù)據(jù)反射(https://www.typescriptlang.org/docs/handbook/decorators.html#metadata)是不兼容的。
寫類型完備的修飾器
推薦寫類型完備的修飾器。如果寫類型完備的修飾器,不免會(huì)用到很多泛型、類型參數(shù),這樣也會(huì)影響代碼的可讀性。怎么寫修飾器后面會(huì)有更多的文檔出來。先推薦了一篇文章:《JavaScript metaprogramming with the 2022-03 decorators API》https://2ality.com/2022/10/javascript-decorators.html
const Type Parameters
提供const
修飾符,對(duì)泛型的類型參數(shù)進(jìn)行修飾。用于解決之前 需要 增加as const
斷言才能實(shí)現(xiàn)的類型推導(dǎo)。
5.0 之前對(duì)于泛型的類型參數(shù)的類型推導(dǎo),TypeScript 往往只能推導(dǎo)到基礎(chǔ)數(shù)據(jù)類型,這樣做是保證變量類型的可變。例子:
但是,從getConstValue
函數(shù)真實(shí)的意圖--得到一個(gè)不可變的常量,來說這樣的類型推導(dǎo)是不完備的。之前我們往往這么做:
在5.0版本中,可以不需要這樣的騷操作了,把const
修飾符加上作用于泛型的類型參數(shù)即可。優(yōu)雅!
與泛型約束一起使用
注意當(dāng)const
修飾符修飾的類型變量,后面帶有泛型約束extends
時(shí),如泛型約束不是常量,那么類型推導(dǎo)的結(jié)果遵循泛型約束,而不是常量。
官網(wǎng)的例子:
作用范圍
const
修飾符的類型推導(dǎo)生效范圍:函數(shù)調(diào)用時(shí),參數(shù)是對(duì)象、數(shù)組或表達(dá)式。如果函數(shù)調(diào)用時(shí),參數(shù)是一個(gè)變量,上面講述的類型推導(dǎo)不會(huì)生效。
官網(wǎng)例子:
compilerOptions 的 extends 配置支持多文件
枚舉
TypeScript 5.0 之前的枚舉,枚舉分為數(shù)字枚舉和字符串枚舉。
TypeScript 5.0 將所有枚舉合并為統(tǒng)一的一種枚舉類型(Union enums),其含義就是枚舉類型是其所有枚舉成員類型組成的聯(lián)合類型。
這樣做帶來什么改變呢?下面列舉了一些 5.0 之前,在使用枚舉時(shí),會(huì)碰到的一些很神奇詭異的規(guī)則,然后跟 5.0 之后做一下對(duì)比,看有什么改變。
前后對(duì)比
5.0 之前:把枚舉成員用作類型,所有枚舉成員必須使用字面量初始化。
5.0 之后:沒有這個(gè)約束了。
github 相關(guān)的#27976(https://github.com/microsoft/TypeScript/issues/27976)
UserResponse
枚舉中,存在UserResponse.Yes``UserResponse.NotSure
兩個(gè)用非字面量初始化的成員,那么使用成員去當(dāng)TypeScript中的類型使用時(shí),就會(huì)報(bào)錯(cuò):
Enum type 'UserResponse' has members with initializers that are not literals.(2535)?
必須把枚舉成員全部使用字面量來初始化,可以對(duì)比著UserResponse_1
來看。
5.0 之前:字符串枚舉成員只能是常量枚舉成員。比如,無法使用字符串變量或者模版字符串給枚舉成員賦值。
5.0 之后:字符串枚舉成員可以是計(jì)算枚舉成員。
5.0 之前:枚舉成員的初始化,存在一些約束。
5.0 之后:約束的第二條由「數(shù)字字面量」放寬到「數(shù)字字面量和數(shù)字常量」。其他的計(jì)算枚舉成員還是會(huì)報(bào)錯(cuò)。
(感覺規(guī)則又變復(fù)雜了。不過用起來會(huì)方便一點(diǎn)。)
疑問
疑問一
那問題來了,5.0 版本之后,類似E_1.a
、E_1.c
這樣(使用數(shù)字常量初始化或帶有數(shù)字常量表達(dá)式)的枚舉成員,是「計(jì)算枚舉成員」呢還是「常量枚舉成員」。
按照「計(jì)算枚舉成員」和「常量枚舉成員」的定義(https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members)
,它是「計(jì)算枚舉成員」按照枚舉成員的初始化約束,它又是「常量枚舉成員」
感覺「計(jì)算枚舉成員」和「常量枚舉成員」的定義(https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members)要修改,到目前官方文檔還沒修改。
moduleResolution 配置新增 bundler支持
TypeScript 4.7? (https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#esm-nodejs)為?--module
?和?--moduleResolution
增加了node16
和nodenext
,從而更好地在 Nodejs 中支持 ESM 標(biāo)準(zhǔn)。但是在這種模式下,會(huì)有很多限制。
比如,在 Nodejs 中,ESM(ECMAScript module)要求在import
相對(duì)路徑的依賴時(shí),需要顯示寫文件的擴(kuò)展名。
這樣做的原因是為了在文件服務(wù)器中有更好的文件搜尋速度。對(duì)比使用其他構(gòu)建工具,node16/nodenext
模式下的這些限制還是太麻煩了,甚至說默認(rèn)的node
模式更好。
但是,默認(rèn)的node
模式已經(jīng)過時(shí)了,大多數(shù)現(xiàn)代構(gòu)建工具混合著使用ESM(ECMAScript module)和CommonJS兩種模塊標(biāo)準(zhǔn)的模塊解析策略。
于是,5.0 中 TypeScript 提供了新的moduleResolution
配置選項(xiàng)bundler
,它同時(shí)兼容 ESM(ECMAScript module) 和 CommonJS 兩種模塊標(biāo)準(zhǔn)的模塊解析策略,但是又沒有 ESM 在 Nodejs 中的限制。
跟 moduleResolution 相關(guān)的幾個(gè)配置項(xiàng)
allowImportingTsExtensions
配置啟用后,import
其他模塊時(shí)允許攜帶.ts
,?.mts
和?.tsx
這三種擴(kuò)展名。這個(gè)配置啟用必須同時(shí)與--noEmit
?--emitDeclarationOnly
這兩個(gè)配置一起啟用。
當(dāng)--moduleResolution
是node16
、nodenext
、bundler
是,配置默認(rèn)啟用。
resolvePackageJsonExports與resolvePackageJsonImports
配置啟用后,import
來自node_modules
中的模塊時(shí),TypeScript 會(huì)去解析模塊對(duì)應(yīng)的package.json
中的exports
和imports
字段。這塊可以去看一下?Conditional exports?(https://nodejs.org/api/packages.html#conditional-exports)的知識(shí)。當(dāng)--moduleResolution
是node16
、nodenext
、bundler
是,配置默認(rèn)啟用。
allowArbitraryExtensions
允許任意的后綴名。當(dāng)在代碼里 import 的模塊擴(kuò)展名不是.js``.jsx``.ts``.tsx
,編譯器會(huì)按以下的規(guī)則去查找該模塊的類型定義文件:{file basename}.d.{extension}.ts
。
官網(wǎng)例子:
默認(rèn)的話,TypeScript 會(huì)提示一個(gè)錯(cuò)誤,讓你知道 TypeScript 無法解析這種文件類型,代碼運(yùn)行時(shí)可能無法正確地導(dǎo)入。但是如果你在 bundler 中正確地配置,可以加上--allowArbitraryExtensions
這個(gè)新的編譯選項(xiàng)來阻止錯(cuò)誤的提示。
在前端編寫 CSS Modules 的時(shí)候,以前是這樣處理 import css/less 的。
現(xiàn)在通過這個(gè)編譯選項(xiàng)可以增加上類型支持了。但是,針對(duì) css/less 等樣式文件,我可能還是會(huì)這樣處理,感覺加上類型沒太大必要。
當(dāng)--moduleResolution
是node16
、nodenext
、bundler
是,配置默認(rèn)啟用。
customConditions
Conditional exports中還支持自定義conditions?(https://nodejs.org/api/packages.html#resolving-user-conditions)。通過這個(gè)配置進(jìn)行支持。
當(dāng)我們需要import
一個(gè)模塊es-module-package
,它的package.json
是下面這樣定義的話? ??
那么tsconfig.json
可以這么寫
當(dāng)--moduleResolution
是node16
、nodenext
、bundler
是,配置默認(rèn)啟用。
verbatimModuleSyntax
提供了一個(gè)新的配置項(xiàng),用于簡化之前 TypeScript 的引用省略功能涉及importsNotUsedAsValues
、preserveValueImports
、isolatedModules
三個(gè)配置項(xiàng)的配置復(fù)雜問題。
支持 export type *
支持使用export * from "module"
和export * as ns from "module"
這樣的語句進(jìn)行類型導(dǎo)出(https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export)。
JSDoc 支持 @satisfies
TypeScript 4.9 的時(shí)候支持了[satisfies](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html)
操作符?,F(xiàn)在在 JSDoc 上也支持@satisfies
注釋,因?yàn)橛胁糠珠_發(fā)者是通過 JSDoc 注釋來給 Javascript 提供類型檢查的。
JSDoc 支持 @overload
TypeScript 中你可以定義同一個(gè)函數(shù)的不同的入?yún)㈩愋突蛘卟煌姆祷刂殿愋汀,F(xiàn)在在 JSDoc 上支持通過@overload
注釋來滿足這個(gè)需求。
tsc build 模式下支持設(shè)置以下幾個(gè)標(biāo)志位
--declaration
--emitDeclarationOnly
--declarationMap
--soureMap
--inlineSourceMap
編輯器中的不區(qū)分大小寫的導(dǎo)入排序
在類似于 Visual Studio 和 VS Code 這樣的編輯器中,TypeScript 為組織和排序?qū)牒蛯?dǎo)出提供支持。不過,對(duì)于何時(shí)排序的列表會(huì)有不同的解釋。
例如,以下導(dǎo)入列表是否已排序?
令人驚訝的是,答案可能是“取決于情況”。如果我們不關(guān)心大小寫,那么這個(gè)列表顯然沒有排序。字母 "f" 在 "t" 和 "T" 之前。
但在大多數(shù)編程語言中,排序默認(rèn)比較字符串的字節(jié)值。JavaScript 比較字符串的方式意味著 "Toggle" 總是排在 "freeze" 之前,因?yàn)楦鶕?jù)?ASCII 字符編碼?(https://en.wikipedia.org/wiki/ASCII),大寫字母在小寫字母之前。因此,從這個(gè)角度來看,導(dǎo)入列表已排序。
TypeScript 以前認(rèn)為導(dǎo)入列表已排序,因?yàn)樗M(jìn)行了基本的區(qū)分大小寫排序。這可能是開發(fā)人員的痛點(diǎn),因?yàn)樗麄兏矚g不區(qū)分大小寫的排序方式,或者使用像 ESLint 這樣的工具默認(rèn)需要不區(qū)分大小寫的排序方式。
TypeScript 現(xiàn)在默認(rèn)檢測(cè)大小寫敏感性。這意味著 TypeScript 和類似 ESLint 的工具通常不會(huì)因?yàn)槿绾巫詈玫嘏判驅(qū)攵盃?zhēng)執(zhí)”。
我們的團(tuán)隊(duì)還在嘗試更多的排序策略,您可以在此處閱讀更多信息(https://github.com/microsoft/TypeScript/pull/52115)。這些選項(xiàng)可能最終可以由編輯器進(jìn)行配置。目前,它們?nèi)匀皇遣环€(wěn)定和實(shí)驗(yàn)性的,您可以通過在 VS Code 中使用 JSON 選項(xiàng)中的?typeScript.unstable?
條目來選擇它們。以下是您可以嘗試的所有選項(xiàng)(默認(rèn)設(shè)置):
完善的 switch/case 代碼補(bǔ)全

??速度、內(nèi)存和包體積的優(yōu)化
官方稱 5.0 版本,不管是代碼結(jié)構(gòu)、數(shù)據(jù)結(jié)構(gòu)還是算法實(shí)現(xiàn)都進(jìn)行了很“強(qiáng)大”的改變。在使用 TypeScript 的時(shí)候,感覺會(huì)變得更快,不僅僅是在運(yùn)行 TypeScript 的時(shí)候,甚至在安裝它的時(shí)候都會(huì)覺得更快。
舉了兩個(gè)例子:
使用 TypeScript 5.0 Beta 構(gòu)建 VS Code 花的時(shí)間只占了使用 4.9 去構(gòu)建的 81%。
TypeScript 5.0 Beta(37.3MB) 包體積是 4.9(63.8MB)的 58%。
TypeScript 5.0 更新了一些值得注意的改進(jìn),下面我們會(huì)逐一介紹。
首先,我們最近將 TypeScript 從命名空間轉(zhuǎn)移到了模塊中,這使我們能夠利用現(xiàn)代構(gòu)建工具來執(zhí)行優(yōu)化,如作用域提升。使用這些工具,重新審視我們的打包策略,并刪除一些廢棄的代碼,使 TypeScript 4.9 的63.8 MB 包大小減小了約 26.4 MB。這也通過直接函數(shù)調(diào)用帶來了顯著的加速。我們?cè)谶@里寫了一篇關(guān)于我們遷移到模塊的詳細(xì)說明(https://devblogs.microsoft.com/typescript/typescripts-migration-to-modules/)。
TypeScript 還增加了對(duì)編譯器內(nèi)部對(duì)象類型的更一致性,并在一些對(duì)象類型上減少了存儲(chǔ)的數(shù)據(jù)。這減少了多態(tài)操作,同時(shí)平衡了由于使我們的對(duì)象結(jié)構(gòu)更統(tǒng)一而帶來的內(nèi)存使用增加。
我們還對(duì)序列化信息到字符串時(shí)進(jìn)行了一些緩存。類型顯示,可能會(huì)作為錯(cuò)誤報(bào)告、聲明發(fā)出、代碼完成等的一部分發(fā)生,可能會(huì)相當(dāng)昂貴。TypeScript現(xiàn)在對(duì)一些常用的機(jī)制進(jìn)行了緩存,以便在這些操作之間重復(fù)使用。
我們所做的另一個(gè)值得注意的改變是,利用var
關(guān)鍵字偶爾規(guī)避在閉包中使用let
和const
所產(chǎn)生的成本,這提高了我們的一些解析性能。
總的來說,我們期望大多數(shù)代碼庫應(yīng)該會(huì)從 TypeScript 5.0 中看到速度的提升,并且一直能夠重復(fù)獲得 10% 到 20% 的勝利。當(dāng)然,這將取決于硬件和代碼庫的特征,但我們鼓勵(lì)您今天就在您的代碼庫上嘗試它!
有關(guān)更多信息,請(qǐng)參見我們的一些值得注意的優(yōu)化:
遷移到模塊?https://github.com/microsoft/TypeScript/pull/51387
節(jié)點(diǎn)單態(tài)化?https://github.com/microsoft/TypeScript/pull/51682
Symbol 單態(tài)化?https://github.com/microsoft/TypeScript/pull/51880
標(biāo)識(shí)符大小減小?https://github.com/microsoft/TypeScript/pull/52170
Printer 緩存?https://github.com/microsoft/TypeScript/pull/52382
有限使用 var?https://github.com/microsoft/TypeScript/issues/52924
??參考
《Announcing TypeScript 5.0》
https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/
《Announcing TypeScript 5.0 Beta》
https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-beta/