TypeScript 4.2 有哪些新特性?
TypeScript 4.2 發(fā)布了!對(duì)于不熟悉 TypeScript 的人來說,TypeScript 就是增加了靜態(tài)類型和類型檢查的 JavaScript。有了類型限制,你就可以精確的表達(dá)你的函數(shù)需要什么類型的參數(shù)以及返回什么類型的結(jié)果。
同時(shí),利用 TypeScript 的類型檢查,你可以很容易避免一些常見錯(cuò)誤,例如拼寫錯(cuò)誤或者忘記處理 null 和 undefined。因?yàn)?TypeScript 代碼看起來就像是增加了類型的 JavaScript,所以你對(duì) JavaScript 所知的一切知識(shí)依然適用于 TypeScript。在需要的時(shí)候,你也可以剝離類型,從而獲得純凈、可讀并且在任何地方可以運(yùn)行的 JavaScript 代碼。如果你想深入了解 TypeScript , 你可以訪問這個(gè)網(wǎng)站[1]。
開始使用 TypeScript 4.2,你可以通過 NuGet[2] 安裝,或使用 npm 運(yùn)行以下命令:
- npm install typescript
讓我們預(yù)覽一下 TypeScript 4.2 的新特性:
- 更智能的類型別名保持
- 元組類型中的前置/中置的擴(kuò)展元素
- 更嚴(yán)格的 in 運(yùn)算符檢查
- --noPropertyAccessFromIndexSignature
- 抽象構(gòu)建簽名
- 通過 --explainFiles 了解你的項(xiàng)目結(jié)構(gòu)
- 改進(jìn)邏輯表達(dá)式中非執(zhí)行函數(shù)的檢查
- 可以將解構(gòu)變量明確標(biāo)記為未使用
- 在可選屬性和字符串索引簽名之間寬松的規(guī)則
- 聲明缺少的幫助函數(shù)
- 破壞性變化
更智能的類型別名保持
TypeScript 有一種方式可以聲明新的類型叫做類型別名。如果你寫了一系列函數(shù)可以處理 string | number | boolean 三種類型的數(shù)據(jù),那么你可以定義一個(gè)類型別名,用于避免重復(fù)工作:
- type BasicPrimitive = number | string | boolean
TypeScript 曾使用一系列的規(guī)則來猜測何時(shí)應(yīng)當(dāng)使用類型別名,而何時(shí)又應(yīng)該把所有類型都打印出來。例如,來看下以下代碼片段:
- export type BasicPrimitive = number | string | boolean;
- export function doStuff(value: BasicPrimitive) {
- let x = value;
- return x;
- }
如果我們?cè)谥T如 Visual Studio、VS Code 編輯器中或者 TypeScript 運(yùn)行環(huán)境[3] 中把鼠標(biāo)懸停在變量 x 上,我們就會(huì)看到一個(gè)快速展示面板,顯示出 x 的類型為 BasicPrimitive。同樣的,如果我們?yōu)檫@個(gè)方法定義一個(gè)聲明文件(.d.ts 文件),TypeScript 將會(huì)顯示 doStuff 返回值的類型為 BasicPrimitive。
但是,如果我們返回的是 BasicPrimitive 或者 undefined 會(huì)發(fā)生什么呢?
- export type BasicPrimitive = number | string | boolean;
- export function doStuff(value: BasicPrimitive) {
- if (Math.random() < 0.5) {
- return undefined;
- }
- return value;
- }
我們可以在TypeScript 運(yùn)行環(huán)境[4]中觀察發(fā)生了什么。盡管我們希望看到 TypeScript 展示的 doStuff 返回值類型是 BasicPrimitive | undefined,但是實(shí)際情況是,顯示的返回值類型為 string | number | boolean | undefined!這是怎么回事?
這要?dú)w因于 TypeScript 內(nèi)部對(duì)于類型的解析方式。當(dāng)創(chuàng)建了一個(gè)聯(lián)合類型包含一個(gè)或多個(gè)其他的聯(lián)合類型時(shí),TypeScript 會(huì)將這些類型歸一化為一個(gè)新的扁平的聯(lián)合類型 —— 此時(shí),原本類型的信息就丟失了。類型檢查器將會(huì)查找 string | number | boolean | undefined 每種組合是否具有類型別名,即使這樣,仍可能會(huì)得到多個(gè) string | number | boolean 類型別名的結(jié)果。
在 TypeScript 4.2 中,我們的內(nèi)部邏輯將更加智能。我們會(huì)通過保留類型的原始定義以及后續(xù)對(duì)其的更新,從而持續(xù)追蹤該類型的構(gòu)造變化。我們同時(shí)也會(huì)追蹤被鍵入其他類型別名實(shí)例的類型別名,并加以區(qū)分。
能夠根據(jù)你在代碼中使用的方式打印出這些類型,意味著對(duì)于 TypeScript 使用者來說,可以避免看到那些令人厭惡的巨型類型定義;并且這種方式可以幫助轉(zhuǎn)化出更優(yōu)質(zhì)的 .d.ts 文件輸出、錯(cuò)誤信息以及在編輯器中對(duì)變量展示的快速信息和幫助等。
更詳細(xì)的內(nèi)容,可以查閱改進(jìn)保留實(shí)例間聯(lián)合和相交類型別名的第一個(gè)Pull Request[5],以及隨后的保留間接別名的第二個(gè)Pull Request[6]。
元組類型中的前置/中置的擴(kuò)展元素
在 TypeScript 中,元組類型最初用于對(duì)特定長度和特定元素類型進(jìn)行建模。
- // 一個(gè)存放了一對(duì)數(shù)字的元組
- let a: [number, number] = [1, 2];
- // 一個(gè)存放了一個(gè)字符串、一個(gè)數(shù)字以及一個(gè)布爾值的元組
- let b: [string, number, boolean] = ["hello", 42, true];
隨著版本的更新,TypeScript 元組變得越來越復(fù)雜,因?yàn)樗鼈冞€被用于對(duì)像 JavaScript 中的參數(shù)列表一樣的事務(wù)進(jìn)行建模。這樣帶來的結(jié)果就是,元組類型可以包含可選元素以及擴(kuò)展元素,甚至為了提高可讀性和易用性,元素還可以擁有標(biāo)簽。
- // 一個(gè)包含一個(gè)或兩個(gè)字符串的元組
- let c: [string, string?] = ["hello"];
- c = ["hello", "world"];
- // 一個(gè)包含一個(gè)或兩個(gè)字符串,并且打了標(biāo)簽的元組
- let d: [first: string, second?: string] = ["hello"];
- d = ["hello", "world"];
- // 一個(gè)包含擴(kuò)展元素的元組 —— 前置元素至少包含兩個(gè)字符串
- // 后置元素可以包含任意多個(gè)布爾值
- let e: [string, string, ...boolean[]];
- e = ["hello", "world"];
- e = ["hello", "world", false];
- e = ["hello", "world", true, false, true];
在 TypeScript 4.2 中,擴(kuò)展元素的使用特別得到了擴(kuò)展。在較早的版本中,TypeScript 只允許擴(kuò)展元素出現(xiàn)在元組的最后位置。
但是現(xiàn)在,擴(kuò)展元素可以出現(xiàn)在元組的任意位置 —— 僅僅需要滿足一些限制條件。
- let foo: [...string[], number];
- foo = [123];
- foo = ["hello", 123];
- foo = ["hello!", "hello!", "hello!", 123];
- let bar: [boolean, ...string[], boolean];
- bar = [true, false];
- bar = [true, "some text", false];
- bar = [true, "some", "separated", "text", false];
想讓擴(kuò)展元素放在元組的任意位置,唯一的限制條件就是:不能有可選元素在其后面的位置并且不能有其他的擴(kuò)展元素。換句話說,每個(gè)元組只允許一個(gè)擴(kuò)展元素,并且該擴(kuò)展元素后序位置不能跟隨一個(gè)可選元素。
- interface Clown { /*...*/ }
- interface Joker { /*...*/ }
- let StealersWheel: [...Clown[], "me", ...Joker[]];
- // ~~~~~~~~~~ Error!
- // 擴(kuò)展元素后續(xù)不能有其他的擴(kuò)展元素
- let StringsAndMaybeBoolean: [...string[], boolean?];
- // ~~~~~~~~ Error!
- // 擴(kuò)展元素后序不能是可選元素
這些靈活的擴(kuò)展元素,可以用于對(duì)具有任意數(shù)量前置參數(shù)以及固定格式后置參數(shù)的函數(shù)進(jìn)行建模。
- declare function doStuff(...args: [...names: string[], shouldCapitalize: boolean]): void;
- doStuff(/*shouldCapitalize:*/ false)
- doStuff("fee", "fi", "fo", "fum", /*shouldCapitalize:*/ true);
盡管 JavaScript 沒有任何語法可以支持前置的擴(kuò)展參數(shù),我們?nèi)钥梢允褂?...args 這種元組類型來實(shí)現(xiàn)上述 doStuff 那樣具有前置擴(kuò)展參數(shù)的函數(shù)。用這種方式可以對(duì)許多現(xiàn)有的 JavaScript 進(jìn)行建模。
更多詳細(xì)信息,請(qǐng)參考這個(gè) Pull Request[7]。
更嚴(yán)格的 in 運(yùn)算符檢查
在 JavaScript 中,在 in 運(yùn)算符右側(cè)使用非對(duì)象類型是一個(gè)運(yùn)行時(shí)錯(cuò)誤。而在 TypeScript 4.2 中,該錯(cuò)誤可以在代碼設(shè)計(jì)時(shí)即被捕獲。
- "foo" in 42
- // ~~
- // 錯(cuò)誤!'in' 表達(dá)式右側(cè)不能為基數(shù)據(jù)類型
這項(xiàng)檢查在大多數(shù)情況下都是相當(dāng)保守的,如果你遇到過相關(guān)的錯(cuò)誤提示,很可能是代碼出了問題。
非常感謝我們的外圍貢獻(xiàn)者 Jonas Hübotter[8] 提出的 Pull Request[9]。
--noPropertyAccessFromIndexSignature
在 TypeScript 首次引入索引簽名時(shí),你只能使用諸如 person["name"] 這種 “中括號(hào)” 元素訪問語法來聲明對(duì)象的屬性。
- interface SomeType {
- /** 這是一個(gè)索引簽名 */
- [propName: string]: any;
- }
- function doStuff(value: SomeType) {
- let x = value["someProperty"];
- }
如果我們需要一個(gè)具有任意屬性的對(duì)象,這種方式將會(huì)非常麻煩。舉個(gè)例子,假設(shè)有一個(gè) API,犯了一個(gè)常見的拼寫錯(cuò)誤——在某個(gè)屬性名字后面多加了一個(gè) s。
- interface Options {
- /** 需要排除的文件格式 */
- exclude?: string[];
- /**
- * 處理未聲明為 'any' 的所有其他屬性
- */
- [x: string]: any;
- }
- function processOptions(opts: Options) {
- // 注意這里我們故意訪問的是 `excludes` 而非 `exclude`
- if (opts.excludes) {
- console.error("The option `excludes` is not valid. Did you mean `exclude`?");
- }
- }
為了使這些類型更易用,前不久,TypeScript 允許了使用 “.” 方法訪問具有字符串索引簽名對(duì)象(例如:person.name)的屬性的語法。這也使得從現(xiàn)有 JavaScript 代碼過渡為 TypeScript 代碼變得容易。
但是,放松限制同時(shí)也意味著拼寫錯(cuò)誤導(dǎo)致的顯示聲明屬性的錯(cuò)誤訪問會(huì)更加容易。
- function processOptions(opts: Options) {
- // ...
- // 注意,這次我們是 “無意間” 訪問了 `excludes`
- // 糟糕!完全奏效。
- for (const excludePattern of opts.excludes) {
- // ...
- }
- }
某些情況下,用戶只希望在顯示聲明的索引簽名中進(jìn)行訪問——他們希望在使用點(diǎn)方法訪問對(duì)象屬性時(shí),如果該屬性不具有顯示聲明,則應(yīng)當(dāng)返回錯(cuò)誤信息。
這就是 TypeScript 引入新的標(biāo)識(shí) --noPropertyAccessFromIndexSignature 的目的。開啟這么模式,你將選擇使用 TypeScript 舊的驗(yàn)證行為,從而在上述過程中拋出錯(cuò)誤。這個(gè)新的設(shè)置不受嚴(yán)格模式的限制,因?yàn)槲覀兿嘈庞脩魰?huì)發(fā)現(xiàn)它在某些特定代碼中會(huì)很有用。
你可以通過閱讀這個(gè) Pull Request[10] 獲取對(duì)這個(gè)功能更詳細(xì)的了解。同時(shí),我們還要非常感謝 Wenlu Wang[11] 向我們提交了這個(gè) Pull Request。
抽象構(gòu)建簽名
TypeScript 允許我們標(biāo)記一個(gè)類為抽象類。這將告訴 TypeScript 該類只能被其他類擴(kuò)展,并且擴(kuò)展類必須包含確切的屬性才能實(shí)例化。
- abstract class Shape {
- abstract getArea(): number;
- }
- // 錯(cuò)誤! 不能實(shí)例化一個(gè)抽象類。
- new Shape();
- class Square extends Shape {
- #sideLength: number;
- constructor(sideLength: number) {
- this.#sideLengthsideLength = sideLength;
- }
- getArea() {
- return this.#sideLength ** 2;
- }
- }
- // 可以正確執(zhí)行
- new Square(42);
為了確保這條限制在新建抽象類的過程中始終有效,你不能將抽象類賦值給任何需要構(gòu)造簽名的對(duì)象。
- interface HasArea {
- getArea(): number;
- }
- // 錯(cuò)誤!不能將抽象構(gòu)造類型賦值給非抽象構(gòu)造類型
- let Ctor: new () => HasArea = Shape;
如果我們?cè)馐菆?zhí)行 new Ctor 這樣的代碼,拋出異常是正確的行為。但如果我們是想編寫 Ctor 的子類,這種限制就顯得過于嚴(yán)格。
- functon makeSubclassWithArea(Ctor: new () => HasArea) {
- return class extends Ctor {
- getArea() {
- // ...
- }
- }
- }
- let MyShape = makeSubclassWithArea(Shape);
同樣的,它同樣不能與內(nèi)置的幫助類,例如 InstanceType,配合使用。
- // 錯(cuò)誤!
- // 類型 'typeof Shape' 不滿足于約束條件 'new (...args: any) => any'。
- // 不能將抽象構(gòu)造類型賦值給非抽象構(gòu)造類型
- type MyInstance = InstanceType<typeof Shape>;
這就是為什么 TypeScript 4.2 要允許你在構(gòu)造簽名中指定抽象指示器。
- interface HasArea {
- getArea(): number;
- }
- // 成功!
- let Ctor: abstract new () => HasArea = Shape;
- // ^^^^^^^^
為構(gòu)造簽名增加抽象指示器,意味著你可以將其在抽象構(gòu)造函數(shù)中傳遞。這不會(huì)阻止你向其傳遞其他的 “具象” 類或構(gòu)造函數(shù),該指示器實(shí)際上只是表示該類不會(huì)直接運(yùn)行構(gòu)造函數(shù),因此可以安全地傳遞任何一種類型的類。
此項(xiàng)功能使我們可以用帶有抽象類的方式編寫 mixin 工廠函數(shù)。舉例來說,在下面這段代碼中,我們可以使用包含了抽象類 SuperClass 的 mixin 函數(shù) withStyles。
- abstract class SuperClass {
- abstract someMethod(): void;
- badda() {}
- }
- type AbstractConstructor<T> = abstract new (...args: any[]) => T
- function withStyles<T extends AbstractConstructor<object>>(Ctor: T) {
- abstract class StyledClass extends Ctor {
- getstyles() {
- // ...
- }
- }
- return StyledClass;
- }
- class SubClass extends withStyles(SuperClass) {
- someMethod() {
- this.someMethod()
- }
- }
請(qǐng)注意,withStyle 展示的是一條特定規(guī)則,必須將擴(kuò)展自抽象構(gòu)造函數(shù)(如 Ctor) 的類 (如 StyledClass) 也聲明為抽象類。這是因?yàn)闊o法知道是否傳入了具有更多抽象成員的類,并且也無法知道子類是否實(shí)現(xiàn)了所有抽象成員。
你可以在這個(gè)Pull Request[12]里,查看更多抽象構(gòu)造簽名的內(nèi)容。
通過 --explainFiles 了解你的項(xiàng)目結(jié)構(gòu)
對(duì)于 TypeScript 用戶來說,一個(gè)令人驚訝又常見的場景是被問到:“為什么 TypeScript 包含這個(gè)文件?” 推斷程序文件是一個(gè)復(fù)雜的過程,這就是為什么一個(gè)特定組合的 lib.d.ts 很有必要,為什么 node_modules 中特定的文件需要被包含以及為什么有些文件我們認(rèn)為應(yīng)該被排除但是結(jié)果卻被包含。
這也就是為什么 TypeScript 現(xiàn)在提供了 --explainFiles 標(biāo)志。
- tsc --explainFiles
當(dāng)你啟用這個(gè)選項(xiàng),TypeScript 編譯器將給出一些非常冗長的輸出,用于說明某個(gè)文件為什么會(huì)出現(xiàn)在程序里。為了方便的閱讀,你可以把輸出內(nèi)容存入一個(gè)文件,或者通過管道輸出到更容易閱讀的程序中。
- # 將輸出內(nèi)容存入文件
- tsc --explainFiles > expanation.txt
- # 通過管道將輸出內(nèi)容發(fā)送到工具程序例如`less`,或者像 VS Code 這種編輯器
- tsc --explainFiles | less
- tsc --explainFiles | code -
通常,輸出內(nèi)容首先會(huì)列出包含 lib.d.ts 的原因,然后是本地文件,最后是 node_modules 文件。
- TS_Compiler_Directory/4.2.2/lib/lib.es5.d.ts
- Library referenced via 'es5' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2015.d.ts'
- TS_Compiler_Directory/4.2.2/lib/lib.es2015.d.ts
- Library referenced via 'es2015' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2016.d.ts'
- TS_Compiler_Directory/4.2.2/lib/lib.es2016.d.ts
- Library referenced via 'es2016' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2017.d.ts'
- TS_Compiler_Directory/4.2.2/lib/lib.es2017.d.ts
- Library referenced via 'es2017' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2018.d.ts'
- TS_Compiler_Directory/4.2.2/lib/lib.es2018.d.ts
- Library referenced via 'es2018' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2019.d.ts'
- TS_Compiler_Directory/4.2.2/lib/lib.es2019.d.ts
- Library referenced via 'es2019' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2020.d.ts'
- TS_Compiler_Directory/4.2.2/lib/lib.es2020.d.ts
- Library referenced via 'es2020' from file 'TS_Compiler_Directory/4.2.2/lib/lib.esnext.d.ts'
- TS_Compiler_Directory/4.2.2/lib/lib.esnext.d.ts
- Library 'lib.esnext.d.ts' specified in compilerOptions
- ... More Library References...
- foo.ts
- Matched by include pattern '**/*' in 'tsconfig.json'
目前,我們還沒發(fā)保證輸出的格式 —— 在后續(xù)的更新中可能會(huì)不斷變化。值得一提的是,如果你對(duì)格式改進(jìn)有任何好的建議,我們都非常感興趣。
更多信息,請(qǐng)參照原始 Pull Request[13]。
改進(jìn)邏輯表達(dá)式中非執(zhí)行函數(shù)的檢查
多虧了 Alex Tarasyuk[14] 的進(jìn)一步改進(jìn),TypeScript 非執(zhí)行函數(shù)檢查現(xiàn)在適用于 && 和 || 表達(dá)式。
現(xiàn)在在 --strictNullChecks模式下,以下代碼會(huì)報(bào)錯(cuò):
- function shouldDisplayElement(element: Element) {
- // ...
- return true;
- }
- function getVisibleItems(elements: Element[]) {
- return elements.filter(e => shouldDisplayElement && e.children.length)
- // ~~~~~~~~~~~~~~~~~~~~
- // 該條件始終會(huì)返回 true,因?yàn)樵摵瘮?shù)已經(jīng)被定義了。
- // 你是否是想執(zhí)行它來代替?
- }
更多信息,詳見 Pull Request[15]。
可以將解構(gòu)變量明確標(biāo)記為未使用
感謝 Alex Tarasyuk[16] 的另一個(gè) Pull Request,你現(xiàn)在可以通過在解構(gòu)變量前加上下劃線,從而將其標(biāo)記為未使用變量。
- let [_first, second] = getValues();
之前的版本中,如果 _first 在之后的代碼中沒有被使用,那么 TypeScript 會(huì)拋出一個(gè) noUnusedLocals 錯(cuò)誤。現(xiàn)在,TypeScript 會(huì)認(rèn)識(shí)到 _first 只聲明不調(diào)用是有意為之。
詳情可見這個(gè) Pull Request [17]。
在可選屬性和字符串索引簽名之間寬松的規(guī)則
字符串索引簽名是用于鍵入類似字典對(duì)象的方式 —— 當(dāng)你想允許該對(duì)象包含使用任意鍵名。
- const movieWatchCount: { [key: string]: number } = {};
- function watchMovie(title: string) {
- movieWatchCount[title] = (movieWatchCount[title] ?? 0) + 1;
- }
當(dāng)然,現(xiàn)在該典中沒有包含任何電影標(biāo)題,movieWatchCount[title] 將返回 undefined (TypeScript 4.1 中新增了一個(gè)選項(xiàng) --noUncheckedIndexedAccess 會(huì)給字符串索引簽名自動(dòng)添加 undefined 可選類型)。即使很明顯,movieWatchCount 必將在之后包含某些字符串,但由于存在 undefined,之前的版本的 TypeScript ,仍然會(huì)將可選對(duì)象屬性視為無法分配給其他兼容的索引簽名。
- type WesAndersonWatchCount = {
- "Fantastic Mr. Fox"?: number;
- "The Royal Tenenbaums"?: number;
- "Moonrise Kingdom"?: number;
- "The Grand Budapest Hotel"?: number;
- };
- declare const wesAndersonWatchCount: WesAndersonWatchCount;
- const movieWatchCount: { [key: string]: number } = wesAndersonWatchCount;
- // ~~~~~~~~~~~~~~~ 錯(cuò)誤!
- // 類型 'WesAndersonWatchCount' 不能賦值給 '{ [key: string]: number; }'.
- // 屬性 '"Fantastic Mr. Fox"' 和索引簽名不兼容。
- // 類型 'number | undefined' 不能賦值給 'number'.
- // 類型 'undefined' 不能賦值給類型 'number'. (2322)
TypeScript 4.2 允許這個(gè)指派。但是,它不允許分配類型為 undefined 的非可選屬性,也不允許將 undefined 寫入特定鍵:
- type BatmanWatchCount = {
- "Batman Begins": number | undefined;
- "The Dark Knight": number | undefined;
- "The Dark Knight Rises": number | undefined;
- };
- declare const batmanWatchCount: BatmanWatchCount;
- // TypeScript 4.2. 依然會(huì)報(bào)錯(cuò)
- // `undefined` 指揮在屬性被標(biāo)記為可選時(shí)才會(huì)被忽略。
- const movieWatchCount: { [key: string]: number } = batmanWatchCount;
- // TypeScript 4.2. 依然會(huì)報(bào)錯(cuò)
- // 索引簽名不允許顯示定義為 `undefined`。
- movieWatchCount["It's the Great Pumpkin, Charlie Brown"] = undefined;
新規(guī)則同樣不適用于數(shù)字索引簽名,因?yàn)樗鼈儽患俣轭愃茢?shù)組的密集型數(shù)據(jù)結(jié)構(gòu)。
你可以通過閱讀這個(gè) Pull Request[18] 更好的理解這條規(guī)則。
聲明缺少的幫助函數(shù)
感謝 Alex Tarasyuk[19] 實(shí)現(xiàn)的來自社區(qū)的 Pull Request[20],我們現(xiàn)在有了一個(gè)在聲明新函數(shù)和方法時(shí)的快速修復(fù)。
圖片
破壞性變化
我們始終致力于將發(fā)布中的破壞性變化降至最低。TypeScript 4.2 包含一些破壞性變化,但我們認(rèn)為它們?cè)谏?jí)過程中時(shí)可控的。
lib.d.ts 升級(jí)
如同每個(gè)版本的 TypeScript 升級(jí),lib.d.ts 的聲明(特別是為 Web 上下文生成的聲明)都會(huì)發(fā)生改變。升級(jí)的變化有很多,但是最終可能最具有破壞性的是 Intl 和 ResizeObserver。
onImplicitAny 錯(cuò)誤適用于寬松的 yield 表達(dá)式
當(dāng)捕獲了 yield 表達(dá)式的值,但是 TypeScript 不能立刻分辨出你打算接受的類型是哪種時(shí)(舉個(gè)例子:yield 表達(dá)式未按上下文類型輸入),TypeScript 現(xiàn)在會(huì)發(fā)出類型隱式聲明為 any 的錯(cuò)誤。
- function* g1() {
- const value = yield 1;
- // ~~~~~~~
- // 錯(cuò)誤!
- // 'yield' 表達(dá)式隱式返回 'any' 類型
- // 因?yàn)槠浒鄙俜祷仡愋妥⑨尩纳善鳌?nbsp;
- }
- function* g2() {
- // 正確。
- // `yield 1` 的結(jié)果未被使用。
- yield 1;
- }
- function* g3() {
- // 正確。
- // `yield 1` 根據(jù)上下文被定義為 'string' 類型。
- const value: string = yield 1;
- }
- function* g3(): Generator<number, void, string> {
- // 正確。
- // 通過 'g3' 的顯式返回結(jié)果,
- // TypeScript 可以知道 'yield 1' 的類型
- const value = yield 1;
- }
詳情可見這個(gè) Pull Request[21]。
擴(kuò)展的未執(zhí)行函數(shù)檢查
如上文所述,當(dāng)啟用 --strictNullChecks 模式時(shí),未執(zhí)行函數(shù)檢查將會(huì)在 && 和 || 表達(dá)式中一致執(zhí)行。這可能會(huì)成為一個(gè)潛在的破壞性,但通常表示現(xiàn)有代碼中存在邏輯錯(cuò)誤。
JavaScript 中的類型參數(shù)不會(huì)被解析未類型參數(shù)
JavaScript 中已經(jīng)不允許使用類型參數(shù),但是在 TypeScript 4.2 中,解析器將以更符合規(guī)范的方式解析它們。所以,當(dāng)在 JavaScript 中編寫以下代碼時(shí):
- f<T>(100)
TypeScript 會(huì)把它解析為以下代碼:
- (f < T) > (100)
當(dāng)你利用 TypeScript 的 API 來解析 JavaScript 文件中的類型構(gòu)造,你可能會(huì)感到困擾。
in 運(yùn)算符不再允許右側(cè)的值為基類型
- "foo" in 42
- // ~~
- // 錯(cuò)誤!'in' 表達(dá)式右側(cè)不允許是基類型
詳細(xì)請(qǐng)參考這個(gè) Pull Request[22]。
元組中的擴(kuò)展運(yùn)算符大小受限
TypeScript 中元組類型可以由任何類型的擴(kuò)展運(yùn)算語法生成
- // 由擴(kuò)展元素生成的元組類型
- type NumStr = [number, string];
- type NumStrNumStr = [...NumStr, ...NumStr];
- // 數(shù)組擴(kuò)展運(yùn)算
- const numStr = [123, "hello"] as const;
- const numStrNumStr = [...numStr, ...numStr] as const;
有時(shí),這些元組類型可能會(huì)意外的變得巨大,這會(huì)使類型檢查花費(fèi)很長時(shí)間。為了防止類型檢查掛起(這在編輯器場景中尤為糟糕),TypeScript 使用一個(gè)限制器來避免這個(gè)情況的發(fā)生。
你可以在這個(gè) Pull Request[23] 中查看詳情。
.d.ts 擴(kuò)展名的文件不能被 Import
- // 必須改為類似于以下類型:
- // - "./foo"
- // - "./foo.js"
- import { Foo } from "./foo.d.ts";
導(dǎo)入路徑應(yīng)反映加載程序在運(yùn)行時(shí)將執(zhí)行的操作。以下幾種導(dǎo)入都是等價(jià)的:
- import { Foo } from "./foo";
- import { Foo } from "./foo.js";
- import { Foo } from "./foo/index.js";
恢復(fù)模板字面推斷
這個(gè)改變是從 TypeScript 4.2 Beta 版中刪除了一個(gè)功能。如果你還沒有升級(jí)到我們最新的一個(gè)穩(wěn)定版,你可以不必關(guān)注這個(gè)。但是,也許你也有興趣簡單了解一下。
TypeScript 4.2 Beta 版包含了一個(gè)對(duì)模板字符串推斷的更改。在這個(gè)更改中,模板字符串字面或者被定義為給定的模板字符串類型或者簡化為多個(gè)字符串字面類型。這些類型當(dāng)被賦值給變量時(shí),都會(huì)被擴(kuò)大為字符串類型。
- declare const yourName: string;
- // 'bar' 是常量。
- // 它擁有類型 '`hello ${string}`'.
- const bar = `hello ${yourName}`;
- // 'baz' 是變量
- // 它擁有類型 'string'.
- let baz = `hello ${yourName}`;
這類似于字符串字面推斷的工作方式。
- // 'bar' 的類型是 '"hello"'.
- const bar = "hello";
- // 'baz' 的類型是 'string'.
- let baz = "hello";
因此,我們認(rèn)為擁有字符串類型的模板字符串表達(dá)式應(yīng)該為 “常量”。但是,從我們所見所得的情況來看,這并不總是可取的。
作為回應(yīng),我們恢復(fù)了這個(gè)功能(以及潛在的破壞性)。如果你確實(shí)想要給一個(gè)模板字符串表達(dá)式定義為字面量類型,你可以在它的后面增加 as const。
- declare const yourName: string;
- // 'bar' 擁有類型 '`hello ${string}`'.
- const bar = `hello ${yourName}` as const;
- // ^^^^^^^^
- // 'baz' 擁有類型 'string'.
- const baz = `hello ${yourName}`;
TypeScript lift Callback 在 visitNode 中使用不同的類型
TypeScript 具有帶lift功能的 visitNode 函數(shù)。現(xiàn)在,lift 需要一個(gè)只讀的 Node[] 而非 NodeArray<Node>。
從技術(shù)上講,這是一個(gè)API重大更改,您可以在此處閱讀更多內(nèi)容。