禁止的空值和真值檢查
也許你編寫了一個正則表示式,卻忘記了對它呼叫 .test(...)
tsif (/0x[0-9a-f]/) {// Oops! This block always runs.// ...}
或者你可能不小心寫成了 =>(建立了一個箭頭函式)而不是 >=(大於或等於運算子)
tsif (x => 0) {// Oops! This block always runs.// ...}
或者你可能嘗試在 ?? 中使用預設值,卻搞混了 ?? 和比較運算子(如 <)的優先順序
tsfunction isValid(value: string | number, options: any, strictness: "strict" | "loose") {if (strictness === "loose") {value = +value}return value < options.max ?? 100;// Oops! This is parsed as (value < options.max) ?? 100}
或者你可能在複雜的表示式中放錯了括號的位置
tsif (isValid(primaryValue, "strict") || isValid(secondaryValue, "strict") ||isValid(primaryValue, "loose" || isValid(secondaryValue, "loose"))) {// ^^^^ 👀 Did we forget a closing ')'?}
這些示例都沒有達到作者預期的效果,但它們在 JavaScript 中都是合法的程式碼。此前,TypeScript 也會默默地接受這些示例。
但經過一番實驗,我們發現透過標記上述類似的可疑示例,可以捕獲到許許多多的 bug。在 TypeScript 5.6 中,編譯器現在可以在語法層面確定真值或空值檢查總是以特定方式評估時,丟擲錯誤。因此,在上面的示例中,你將開始看到錯誤提示。
tsif (/0x[0-9a-f]/) {// ~~~~~~~~~~~~// error: This kind of expression is always truthy.}if (x => 0) {// ~~~~~~// error: This kind of expression is always truthy.}function isValid(value: string | number, options: any, strictness: "strict" | "loose") {if (strictness === "loose") {value = +value}return value < options.max ?? 100;// ~~~~~~~~~~~~~~~~~~~// error: Right operand of ?? is unreachable because the left operand is never nullish.}if (isValid(primaryValue, "strict") || isValid(secondaryValue, "strict") ||isValid(primaryValue, "loose" || isValid(secondaryValue, "loose"))) {// ~~~~~~~// error: This kind of expression is always truthy.}
透過啟用 ESLint 的 no-constant-binary-expression 規則也可以實現類似的效果,你可以檢視他們在部落格文章中取得的一些成果;但 TypeScript 執行的新檢查與 ESLint 規則並不完全重疊,而且我們認為將這些檢查內建到 TypeScript 本身中也具有很大價值。
請注意,某些表示式即使總是真值或空值,仍然是被允許的。具體來說,true、false、0 和 1 儘管總是真值或假值,但仍被允許,因為像下面這樣的程式碼
tswhile (true) {doStuff();if (something()) {break;}doOtherStuff();}
仍然是慣用且有用的,而像下面這樣的程式碼
tsif (true || inDebuggingOrDevelopmentEnvironment()) {// ...}
在遍歷/除錯程式碼時也很有用。
如果你對該功能的實現或它所捕獲的 bug 型別感興趣,可以檢視實現此功能的拉取請求。
迭代器輔助方法
JavaScript 有可迭代物件(iterables)的概念(我們可以透過呼叫 [Symbol.iterator]() 並獲得迭代器來遍歷這些物件)以及迭代器(iterators)的概念(具有 next() 方法的物件,我們可以呼叫該方法來在遍歷時獲取下一個值)。大體上,當你將它們放入 for/of 迴圈或透過 [...spread] 展開到新陣列中時,通常不需要考慮這些。但 TypeScript 確實使用 Iterable 和 Iterator 型別(甚至還有同時作為兩者的 IterableIterator!)對這些進行了建模,這些型別描述了 for/of 等構造在其上工作所需的最小成員集合。
Iterable(和 IterableIterator)很好,因為它們可以在 JavaScript 中的各種地方使用——但很多人發現自己懷念 Array 上的方法,比如 map、filter,以及出於某種原因的 reduce。這就是為什麼最近 ECMAScript 提出了一項提案,將 Array 中的許多方法(甚至更多)新增到 JavaScript 中產生的多數 IterableIterator 上。
例如,每個生成器現在都會產生一個同時擁有 map 方法和 take 方法的物件。
tsfunction* positiveIntegers() {let i = 1;while (true) {yield i;i++;}}const evenNumbers = positiveIntegers().map(x => x * 2);// Output:// 2// 4// 6// 8// 10for (const value of evenNumbers.take(5)) {console.log(value);}
對於 Map 和 Set 上的 keys()、values() 和 entries() 等方法也是如此。
tsfunction invertKeysAndValues<K, V>(map: Map<K, V>): Map<V, K> {return new Map(map.entries().map(([k, v]) => [v, k]));}
你還可以擴充套件新的 Iterator 物件
ts/*** Provides an endless stream of `0`s.*/class Zeroes extends Iterator<number> {next() {return { value: 0, done: false } as const;}}const zeroes = new Zeroes();// Transform into an endless stream of `1`s.const ones = zeroes.map(x => x + 1);
並且你可以使用 Iterator.from 將任何現有的 Iterable 或 Iterator 適配到這種新型別中
tsIterator.from(...).filter(someFunction);
現在,我們必須討論一下命名問題。
前面我們提到 TypeScript 有 Iterable 和 Iterator 的型別;然而,正如我們所提到的,它們的作用類似於“協議”,以確保某些操作能正常工作。這意味著並不是每個在 TypeScript 中宣告為 Iterable 或 Iterator 的值都會擁有上述提到的那些方法。
但確實存在一個名為 Iterator 的新的執行時值。你可以將 Iterator 以及 Iterator.prototype 作為 JavaScript 中的實際值進行引用。這有點尷尬,因為 TypeScript 已經定義了自己用於型別檢查的 Iterator。因此,由於這種不幸的名稱衝突,TypeScript 需要引入一個單獨的型別來描述這些原生的/內建的迭代器。
TypeScript 5.6 引入了一個名為 IteratorObject 的新型別。它的定義如下
tsinterface IteratorObject<T, TReturn = unknown, TNext = unknown> extends Iterator<T, TReturn, TNext> {[Symbol.iterator](): IteratorObject<T, TReturn, TNext>;}
許多內建集合和方法都會產生 IteratorObject 的子型別(如 ArrayIterator、SetIterator、MapIterator 等),並且 lib.d.ts 中的核心 JavaScript 和 DOM 型別,以及 @types/node,都已經更新以使用這種新型別。
類似地,為了保持一致性,還有一個 AsyncIteratorObject 型別。AsyncIterator 目前在 JavaScript 中尚未作為帶來相同 AsyncIterable 方法的執行時值存在,但它是一項活躍的提案,而這種新型別正是為其做準備。
我們要感謝 Kevin Gibbons,他貢獻了這些型別的更改,並且他是該提案的共同作者之一。
嚴格內建迭代器檢查(及 --strictBuiltinIteratorReturn)
當你呼叫 Iterator<T, TReturn> 上的 next() 方法時,它會返回一個帶有 value 和 done 屬性的物件。這是透過 IteratorResult 型別來建模的。
tstype IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;interface IteratorYieldResult<TYield> {done?: false;value: TYield;}interface IteratorReturnResult<TReturn> {done: true;value: TReturn;}
這裡的命名靈感來自於生成器函式的工作方式。生成器函式可以 yield 值,然後 return 一個最終值——但兩者之間的型別可能是不相關的。
tsfunction abc123() {yield "a";yield "b";yield "c";return 123;}const iter = abc123();iter.next(); // { value: "a", done: false }iter.next(); // { value: "b", done: false }iter.next(); // { value: "c", done: false }iter.next(); // { value: 123, done: true }
有了新的 IteratorObject 型別,我們發現允許安全實現 IteratorObject 存在一些困難。同時,在 TReturn 為 any(預設值!)的情況下,IteratorResult 長期存在不安全性。例如,假設我們有一個 IteratorResult<string, any>。如果我們最終獲取此型別的 value,我們將得到 string | any,這實際上就是 any。
tsfunction* uppercase(iter: Iterator<string, any>) {while (true) {const { value, done } = iter.next();yield value.toUppercase(); // oops! forgot to check for `done` first and misspelled `toUpperCase`if (done) {return;}}}
在今天修復每一個 Iterator 會很難,因為它會引入很多中斷性更改,但我們至少可以用大多數新建立的 IteratorObject 來解決它。
TypeScript 5.6 引入了一個名為 BuiltinIteratorReturn 的新內建型別和一個名為 --strictBuiltinIteratorReturn 的新 --strict 模式標誌。每當 IteratorObject 在 lib.d.ts 等地方使用時,它們總是為 TReturn 編寫 BuiltinIteratorReturn 型別(儘管你更經常看到的是更具體的 MapIterator、ArrayIterator、SetIterator)。
tsinterface MapIterator<T> extends IteratorObject<T, BuiltinIteratorReturn, unknown> {[Symbol.iterator](): MapIterator<T>;}// ...interface Map<K, V> {// .../*** Returns an iterable of key, value pairs for every entry in the map.*/entries(): MapIterator<[K, V]>;/*** Returns an iterable of keys in the map*/keys(): MapIterator<K>;/*** Returns an iterable of values in the map*/values(): MapIterator<V>;}
預設情況下,BuiltinIteratorReturn 為 any,但當啟用了 --strictBuiltinIteratorReturn(可能透過 --strict)時,它為 undefined。在這種新模式下,如果我們使用 BuiltinIteratorReturn,我們之前的示例現在會正確報錯
tsfunction* uppercase(iter: Iterator<string, BuiltinIteratorReturn>) {while (true) {const { value, done } = iter.next();yield value.toUppercase();// ~~~~~ ~~~~~~~~~~~// error! ┃ ┃// ┃ ┗━ Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?// ┃// ┗━ 'value' is possibly 'undefined'.if (done) {return;}}}
你通常會看到 BuiltinIteratorReturn 與 IteratorObject 在 lib.d.ts 中配套使用。通常,我們建議在自己的程式碼中儘可能明確地指定 TReturn。
欲瞭解更多資訊,你可以在此處閱讀有關該功能的介紹。
支援任意模組識別符號
JavaScript 允許模組將帶有無效識別符號名稱的繫結匯出為字串字面量
tsconst banana = "🍌";export { banana as "🍌" };
同樣,它也允許模組獲取帶有這些任意名稱的匯入,並將它們繫結到有效的識別符號上
tsimport { "🍌" as banana } from "./foo"/*** om nom nom*/function eat(food: string) {console.log("Eating", food);};eat(banana);
這看起來像是一個巧妙的技巧,但它在與其他語言進行互操作(通常透過 JavaScript/WebAssembly 邊界)時有其用途,因為其他語言對於什麼是有效識別符號可能有不同的規則。它對於生成程式碼的工具也非常有用,比如帶有 inject 功能的 esbuild 及其 inject 特性。
TypeScript 5.6 現在允許你在程式碼中使用這些任意模組識別符號!我們要感謝 Evan Wallace,他為 TypeScript 貢獻了此項更改!
--noUncheckedSideEffectImports 選項
在 JavaScript 中,可以 import 一個模組而不實際匯入其中的任何值。
tsimport "some-module";
這些匯入通常被稱為副作用匯入(side effect imports),因為它們唯一提供的有用行為就是執行某些副作用(例如註冊全域性變數或向原型新增 polyfill)。
在 TypeScript 中,這種語法有一個非常奇怪的特性:如果 import 可以解析為有效的原始檔,則 TypeScript 會載入並檢查該檔案。另一方面,如果找不到原始檔,TypeScript 會靜默忽略該 import!
這是一種令人驚訝的行為,但它部分源於對 JavaScript 生態系統中建模模式的需求。例如,這種語法也被用於打包工具中的特殊載入器,以載入 CSS 或其他資產。你的打包工具可能配置得讓你透過編寫如下程式碼來包含特定的 .css 檔案
tsximport "./button-component.css";export function Button() {// ...}
儘管如此,這掩蓋了副作用匯入中的潛在拼寫錯誤。這就是為什麼 TypeScript 5.6 引入了一個新的編譯器選項 --noUncheckedSideEffectImports 來捕獲這些情況。啟用 --noUncheckedSideEffectImports 後,如果 TypeScript 找不到副作用匯入的原始檔,現在會報錯。
tsimport "oops-this-module-does-not-exist";// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// error: Cannot find module 'oops-this-module-does-not-exist' or its corresponding type declarations.
啟用此選項時,一些工作的程式碼可能會收到錯誤,例如上面的 CSS 示例。為了解決這個問題,只想為資產編寫副作用 import 的使用者可以更好地利用所謂的環境模組宣告(ambient module declaration)配合萬用字元說明符。它會放在全域性檔案中,看起來像下面這樣
ts// ./src/globals.d.ts// Recognize all CSS files as module imports.declare module "*.css" {}
事實上,你的專案中可能已經有了這樣的檔案!例如,執行 vite init 之類的命令可能會建立一個類似的 vite-env.d.ts。
雖然此選項預設情況下是關閉的,但我們鼓勵使用者嘗試一下!
欲瞭解更多資訊,請檢視此處的實現。
--noCheck 選項
TypeScript 5.6 引入了一個新的編譯器選項 --noCheck,它允許你跳過所有輸入檔案的型別檢查。這避免了在執行輸出檔案所需的任何語義分析時進行不必要的型別檢查。
一種場景是將 JavaScript 檔案生成與型別檢查分離,以便兩者可以作為單獨的階段執行。例如,你可以在迭代時執行 tsc --noCheck,然後執行 tsc --noEmit 進行徹底的型別檢查。你甚至可以在 --watch 模式下並行執行這兩個任務,儘管請注意,如果你真的同時執行它們,你可能需要指定一個單獨的 --tsBuildInfoFile 路徑。
--noCheck 也可用於以類似方式發出宣告檔案。在符合 --isolatedDeclarations 的專案中指定 --noCheck 時,TypeScript 可以快速生成宣告檔案而無需進行型別檢查。生成的宣告檔案將純粹依賴於快速的語法轉換。
請注意,在指定了 --noCheck 但專案不使用 --isolatedDeclarations 的情況下,TypeScript 仍可能執行生成 .d.ts 檔案所需的必要型別檢查。從這個意義上講,--noCheck 這個名字有點名不副實;然而,該過程將比完全型別檢查更“懶惰”,僅計算未標註宣告的型別。這應該比完全型別檢查快得多。
noCheck 也可透過 TypeScript API 作為標準選項使用。在內部,transpileModule 和 transpileDeclaration 已經使用 noCheck 來加速處理(至少從 TypeScript 5.5 開始)。現在任何構建工具都應該能夠利用該標誌,採用各種自定義策略來協調和加速構建。
欲瞭解更多資訊,請參閱TypeScript 5.5 中在內部增強 noCheck 所做的工作,以及在命令列上使其公開可用的相關工作,以及
允許 --build 存在中間錯誤
TypeScript 的專案引用(project references)概念允許你將程式碼庫組織成多個專案並建立它們之間的依賴關係。在 --build 模式(或簡稱為 tsc -b)下執行 TypeScript 編譯器是跨專案進行實際構建並找出哪些專案和檔案需要編譯的內建方式。
以前,使用 --build 模式會假定 --noEmitOnError,如果遇到任何錯誤,會立即停止構建。這意味著如果任何“上游”依賴項有構建錯誤,“下游”專案就永遠無法被檢查和構建。理論上,這是一種非常合理的做法——如果一個專案有錯誤,它對於其依賴項來說不一定處於連貫的狀態。
實際上,這種剛性使得升級等操作變得非常痛苦。例如,如果 projectB 依賴於 projectA,那麼更熟悉 projectB 的人無法主動升級他們的程式碼,直到他們的依賴項被升級。他們會被升級 projectA 的工作所阻塞。
從 TypeScript 5.6 開始,即使依賴項中存在中間錯誤,--build 模式也會繼續構建專案。面對中間錯誤時,它們將被持續報告,輸出檔案將盡力生成;但是,構建將繼續執行直到指定專案完成。
如果你想在第一個出現錯誤的專案上停止構建,可以使用一個名為 --stopOnBuildErrors 的新標誌。這在 CI 環境中執行或在被其他專案大量依賴的專案上進行迭代時非常有用。
請注意,為了實現這一點,TypeScript 現在總是為 --build 呼叫中的任何專案發出 .tsbuildinfo 檔案(即使未指定 --incremental/--composite)。這是為了跟蹤 --build 是如何呼叫的以及將來需要執行哪些工作。
編輯器中的區域優先順序診斷
當 TypeScript 的語言服務被要求對一個檔案進行診斷(如錯誤、建議和棄用提示)時,它通常需要檢查整個檔案。大多數時候這沒問題,但在超大檔案中,這可能會導致延遲。這很令人沮喪,因為修復拼寫錯誤應該感覺像是一個快速操作,但在足夠大的檔案中可能需要幾秒鐘。
為了解決這個問題,TypeScript 5.6 引入了一項稱為區域優先順序診斷(region-prioritized diagnostics)或區域優先順序檢查(region-prioritized checking)的新功能。編輯器現在不僅可以請求一組檔案的診斷,還可以提供給定檔案的相關區域——意圖是這通常是使用者當前可見的檔案區域。TypeScript 語言伺服器隨後可以選擇提供兩組診斷:一組用於該區域,一組用於整個檔案。這使得在大檔案中的編輯感覺快得多,這樣你就不必等待那麼久讓那些紅色波浪線消失了。
具體數字方面,在我們對 TypeScript 自身的 checker.ts 進行測試時,完整的語義診斷響應需要 3330 毫秒。相比之下,第一個基於區域的診斷響應僅需 143 毫秒!雖然剩餘的整個檔案響應需要約 3200 毫秒,但這對於快速編輯來說可能產生巨大的差異。
此功能還包含了大量工作,以使診斷在你的整個體驗中報告得更加一致。由於我們的型別檢查器利用快取來避免重複工作,不同位置間相同型別的檢查往往可能導致不同的(通常更簡短的)錯誤訊息。從技術上講,惰性的亂序檢查可能導致診斷在編輯器中的兩個位置之間報告不同——甚至在此功能之前也是如此——但我們不想加劇這個問題。透過最近的工作,我們已經消除了許多此類錯誤不一致的情況。
目前,此功能在 Visual Studio Code 中適用於 TypeScript 5.6 及更高版本。
欲瞭解更詳細的資訊,請檢視此處的實現和總結。
細粒度提交字元
TypeScript 的語言服務現在為每個補全項提供其自己的提交字元(commit characters)。提交字元是指當輸入時會自動提交當前建議的補全項的特定字元。
這意味著隨著時間的推移,你的編輯器現在會在你輸入某些字元時更頻繁地提交當前建議的補全項。例如,看看下面的程式碼
tsdeclare let food: {eat(): any;}let f = (foo/**/
如果我們的游標在 /**/ 處,不清楚我們正在編寫的程式碼是 let f = (food.eat()) 還是 let f = (foo, bar) => foo + bar。你可以想象編輯器可能會根據我們接下來輸入的字元以不同方式自動補全。例如,如果我們輸入句點字元 (.),我們可能希望編輯器使用變數 food 進行補全;但如果我們輸入逗號字元 (,),我們可能正在編寫箭頭函式的引數。
不幸的是,以前 TypeScript 只是向編輯器發出訊號,表示當前文字可能定義了新的引數名稱,因此沒有任何提交字元是安全的。所以即使“很明顯”編輯器應該使用單詞 food 進行自動補全,輸入 . 也不會做任何事情。
TypeScript 現在明確列出了哪些字元對於每個補全項是安全提交的。雖然這不會立即改變你的日常體驗,但支援這些提交字元的編輯器應該會隨著時間的推移看到行為上的改進。要立即檢視這些改進,你現在可以使用 TypeScript 夜間擴充套件配合 Visual Studio Code Insiders。在上面的程式碼中輸入 . 會正確地使用 food 自動補全。
欲瞭解更多資訊,請參閱新增提交字元的拉取請求,以及我們根據上下文調整提交字元的調整。
自動匯入的排除模式
TypeScript 的語言服務現在允許你指定正則表示式模式列表,從而過濾掉來自某些說明符的自動匯入建議。例如,如果你想從 lodash 等包中排除所有“深層”匯入,你可以在 Visual Studio Code 中配置以下偏好設定
json{"typescript.preferences.autoImportSpecifierExcludeRegexes": ["^lodash/.*$"]}
或者換一種方式,你可能希望禁止從包的入口點匯入
json{"typescript.preferences.autoImportSpecifierExcludeRegexes": ["^lodash$"]}
甚至可以透過使用以下設定來避免 node: 匯入
json{"typescript.preferences.autoImportSpecifierExcludeRegexes": ["^node:"]}
請注意,如果你想指定 i 或 u 等標誌,你需要用斜槓包圍你的正則表示式。提供周圍的斜槓時,你需要轉義內部的其他斜槓。
json{"typescript.preferences.autoImportSpecifierExcludeRegexes": ["^./lib/internal", // no escaping needed"/^.\\/lib\\/internal/", // escaping needed - note the leading and trailing slashes"/^.\\/lib\\/internal/i" // escaping needed - we needed slashes to provide the 'i' regex flag]}
在 Visual Studio Code 中,同樣的設定可以透過 javascript.preferences.autoImportSpecifierExcludeRegexes 應用於 JavaScript。
欲瞭解更多資訊,請參見此處的實現。
顯著的行為更改
本節重點介紹了一組值得注意的更改,在進行任何升級時都應予以確認和理解。有時它會突出顯示棄用、移除和新的限制。它也可能包含功能上有所改進但透過引入新錯誤而可能影響現有構建的錯誤修復。
lib.d.ts
為 DOM 生成的型別可能會對你程式碼庫的型別檢查產生影響。欲瞭解更多資訊,請參閱與此版本 TypeScript 的 DOM 和 lib.d.ts 更新相關的連結問題。
.tsbuildinfo 總是會被寫入
為了使 --build 能夠在依賴項中存在中間錯誤時繼續構建專案,並支援命令列上的 --noCheck,TypeScript 現在總是為 --build 呼叫中的任何專案發出 .tsbuildinfo 檔案。這與 --incremental 是否實際開啟無關。在此處檢視更多資訊。
尊重 node_modules 內的副檔名和 package.json
在 Node.js 於 v12 中實現對 ECMAScript 模組的支援之前,TypeScript 從來沒有很好的方法來知道它在 node_modules 中找到的 .d.ts 檔案是作為 CommonJS 還是 ECMAScript 模組編寫的 JavaScript 檔案。當 npm 的絕大多數包都是純 CommonJS 時,這並不會造成很多問題——如果無法確定,TypeScript 只要假設所有東西都像 CommonJS 那樣行為即可。遺憾的是,如果該假設是錯誤的,它可能會允許不安全的匯入
ts// node_modules/dep/index.d.tsexport declare function doSomething(): void;// index.ts// Okay if "dep" is a CommonJS module, but fails if// it's an ECMAScript module - even in bundlers!import dep from "dep";dep.doSomething();
實際上,這種情況並不常見。但在 Node.js 開始支援 ECMAScript 模組後的幾年裡,ESM 在 npm 上的份額有所增長。幸運的是,Node.js 還引入了一種機制,可以幫助 TypeScript 確定檔案是 ECMAScript 模組還是 CommonJS 模組:.mjs 和 .cjs 副檔名以及 package.json 中的 "type" 欄位。TypeScript 4.7 增加了對理解這些指示符以及編寫 .mts 和 .cts 檔案的支援;然而,TypeScript 僅在 --module node16 和 --module nodenext 下讀取這些指示符,因此對於使用 --module esnext 和 --moduleResolution bundler 的人來說,上面的不安全匯入仍然是一個問題。
為了解決這個問題,TypeScript 5.6 會收集模組格式資訊,並使用它來解決所有 module 模式(除了 amd、umd 和 system)中類似上述示例的歧義。格式特定的副檔名(.mts 和 .cts)在任何發現它們的地方都會被尊重,並且無論 module 設定如何,都會諮詢 node_modules 依賴項內的 package.json "type" 欄位。以前,在技術上可能產生 CommonJS 輸出到 .mjs 檔案,反之亦然
ts// main.mtsexport default "oops";// $ tsc --module commonjs main.mts// main.mjsObject.defineProperty(exports, "__esModule", { value: true });exports.default = "oops";
現在,.mts 檔案永遠不會發出 CommonJS 輸出,而 .cts 檔案永遠不會發出 ESM 輸出。
請注意,此行為的大部分是在 TypeScript 5.5 的預釋出版本中提供的(實現細節在此處),但在 5.6 中,此行為僅擴充套件到 node_modules 內的檔案。
更多詳細資訊可在此處的更改中獲得。
糾正計算屬性上的 override 檢查
此前,標記為 override 的計算屬性無法正確檢查基類成員是否存在。同樣,如果你使用了 noImplicitOverride,如果你忘記給計算屬性新增 override 修飾符,也不會收到錯誤。
TypeScript 5.6 現在在這兩種情況下都能正確檢查計算屬性。
tsconst foo = Symbol("foo");const bar = Symbol("bar");class Base {[bar]() {}}class Derived extends Base {override [foo]() {}// ~~~~~// error: This member cannot have an 'override' modifier because it is not declared in the base class 'Base'.[bar]() {}// ~~~~~// error under noImplicitOverride: This member must have an 'override' modifier because it overrides a member in the base class 'Base'.}
此修復得益於 Oleksandr Tarasiuk 在此拉取請求中的貢獻。