strictBindCallApply
TypeScript 3.2 引入了一個新的 strictBindCallApply 編譯器選項(屬於 strict 選項族),使用該選項後,函式物件上的 bind、call 和 apply 方法將被強型別化並受到嚴格檢查。
tsfunction foo(a: number, b: string): string {return a + b;}let a = foo.apply(undefined, [10]); // error: too few argumentslet b = foo.apply(undefined, [10, 20]); // error: 2nd argument is a numberlet c = foo.apply(undefined, [10, "hello", 30]); // error: too many argumentslet d = foo.apply(undefined, [10, "hello"]); // okay! returns a string
這是透過在 lib.d.ts 中引入兩個新型別 CallableFunction 和 NewableFunction 來實現的。這些型別分別包含針對常規函式和建構函式方法的 bind、call 和 apply 的專用泛型方法宣告。這些宣告使用泛型剩餘引數(參見 #24897)以強型別方式捕獲並反映引數列表。在 strictBindCallApply 模式下,這些宣告將取代 Function 型別提供的(非常寬鬆的)宣告。
注意事項
由於更嚴格的檢查可能會揭示以前未報告的錯誤,因此在 strict 模式下,這是一個破壞性變更。
此外,此新功能的另一個注意事項是,由於某些限制,bind、call 和 apply 尚無法完全模擬泛型函式或具有過載的函式。當在泛型函式上使用這些方法時,型別引數將被替換為空物件型別 ({});當在具有過載的函式上使用時,僅會模擬最後一個過載。
物件字面量中的泛型展開表示式
在 TypeScript 3.2 中,物件字面量現在允許使用泛型展開表示式,這些表示式現在會產生交叉型別,類似於 Object.assign 函式和 JSX 字面量。例如:
tsfunction taggedObject<T, U extends string>(obj: T, tag: U) {return { ...obj, tag }; // T & { tag: U }}let x = taggedObject({ x: 10, y: 20 }, "point"); // { x: number, y: number } & { tag: "point" }
屬性賦值和非泛型展開表示式在泛型展開表示式的兩側會盡可能地合併。例如:
tsfunction foo1<T>(t: T, obj1: { a: string }, obj2: { b: string }) {return { ...obj1, x: 1, ...t, ...obj2, y: 2 }; // { a: string, x: number } & T & { b: string, y: number }}
非泛型展開表示式繼續像以前一樣處理:呼叫和構造簽名被剝離,僅保留非方法屬性,對於同名屬性,使用最右側屬性的型別。這與交叉型別形成對比,交叉型別會連線呼叫和構造簽名、保留所有屬性,並對同名屬性的型別取交集。因此,當透過泛型型別例項化建立相同型別的展開時,可能會產生不同的結果。
tsfunction spread<T, U>(t: T, u: U) {return { ...t, ...u }; // T & U}declare let x: { a: string; b: number };declare let y: { b: string; c: boolean };let s1 = { ...x, ...y }; // { a: string, b: string, c: boolean }let s2 = spread(x, y); // { a: string, b: number } & { b: string, c: boolean }let b1 = s1.b; // stringlet b2 = s2.b; // number & string
泛型物件剩餘變數和引數
TypeScript 3.2 還允許從泛型變數中解構剩餘繫結。這是透過使用 lib.d.ts 中預定義的 Pick 和 Exclude 輔助型別,並結合使用相關的泛型型別以及解構模式中其他繫結的名稱來實現的。
tsfunction excludeTag<T extends { tag: string }>(obj: T) {let { tag, ...rest } = obj;return rest; // Pick<T, Exclude<keyof T, "tag">>}const taggedPoint = { x: 10, y: 20, tag: "point" };const point = excludeTag(taggedPoint); // { x: number, y: number }
BigInt
BigInt 是 ECMAScript 中一項即將推出的提案的一部分,它允許我們模擬理論上任意大的整數。TypeScript 3.2 帶來了對 BigInt 的型別檢查,以及在以 esnext 為目標時對 BigInt 字面量的輸出支援。
TypeScript 中的 BigInt 支援引入了一種名為 bigint(全小寫)的新基本型別。你可以透過呼叫 BigInt() 函式或在任何整數數值字面量末尾新增 n 來書寫 BigInt 字面量,從而獲得 bigint 型別。
tslet foo: bigint = BigInt(100); // the BigInt functionlet bar: bigint = 100n; // a BigInt literal// *Slaps roof of fibonacci function*// This bad boy returns ints that can get *so* big!function fibonacci(n: bigint) {let result = 1n;for (let last = 0n, i = 0n; i < n; i++) {const current = result;result += last;last = current;}return result;}fibonacci(10000n);
雖然你可能認為 number 和 bigint 之間有密切的互動,但兩者屬於不同的領域。
tsdeclare let foo: number;declare let bar: bigint;foo = bar; // error: Type 'bigint' is not assignable to type 'number'.bar = foo; // error: Type 'number' is not assignable to type 'bigint'.
正如 ECMAScript 規範所要求的那樣,在算術運算中混合使用 number 和 bigint 是錯誤的。你必須顯式地將值轉換為 BigInt。
tsconsole.log(3.141592 * 10000n); // errorconsole.log(3145 * 10n); // errorconsole.log(BigInt(3145) * 10n); // okay!
同樣需要注意的是,使用 typeof 運算子時,bigint 會產生一個新的字串:字串 "bigint"。因此,TypeScript 會如你所願,正確地使用 typeof 進行型別收窄。
tsfunction whatKindOfNumberIsIt(x: number | bigint) {if (typeof x === "bigint") {console.log("'x' is a bigint!");} else {console.log("'x' is a floating-point number");}}
我們要衷心感謝 Caleb Sander 為此功能所做的一切工作。我們非常感謝他的貢獻,相信我們的使用者也會如此!
注意事項
正如我們提到的,BigInt 支援僅適用於 esnext 目標。可能不那麼明顯,由於 BigInt 對於數學運算子(如 +、-、* 等)具有不同的行為,因此為該功能不存在的舊版本目標(如 es2017 及以下)提供支援將涉及重寫每一個此類操作。TypeScript 需要根據型別分派到正確的行為,因此每次加法、字串連線、乘法等都將涉及函式呼叫。
出於這個原因,我們目前沒有計劃提供降級支援。好訊息是,Node 11 和較新版本的 Chrome 已經支援此功能,因此當以 esnext 為目標時,你可以在這些環境中使用 BigInt。
某些目標可能包含 polyfill 或類似 BigInt 的執行時物件。對於這些目的,你可能需要在編譯器選項的 lib 設定中新增 esnext.bigint。
非單元型別作為聯合判別式
TypeScript 3.2 透過放寬被視為判別屬性的規則,使型別收窄變得更容易。只要聯合型別的公共屬性包含*某個*單例型別(例如字串字面量、null 或 undefined),並且不包含泛型,它們現在就被視為判別式。
因此,TypeScript 3.2 將以下示例中的 error 屬性視為判別式,而以前則不會,因為 Error 不是單例型別。得益於此,型別收窄在 unwrap 函式的主體中可以正常工作。
tstype Result<T> = { error: Error; data: null } | { error: null; data: T };function unwrap<T>(result: Result<T>) {if (result.error) {// Here 'error' is non-nullthrow result.error;}// Now 'data' is non-nullreturn result.data;}
透過 Node.js 包進行 tsconfig.json 繼承
TypeScript 3.2 現在可以解析來自 node_modules 的 tsconfig.json。當在 tsconfig.json 的 extends 欄位中使用基礎路徑時,TypeScript 將為我們深入檢索 node_modules 包。
{"": "@my-team/tsconfig-base","": ["./**/*"],"": {// Override certain options on a project-by-project basis."": false}}
在這裡,TypeScript 將向上遍歷 node_modules 資料夾以查詢 @my-team/tsconfig-base 包。對於每個包,TypeScript 將首先檢查 package.json 是否包含 "tsconfig" 欄位,如果有,TypeScript 將嘗試從該欄位載入配置檔案。如果兩者都不存在,TypeScript 將嘗試讀取根目錄下的 tsconfig.json。這類似於 Node 用於查詢包中 .js 檔案的過程,以及 TypeScript 已經使用的 .d.ts 查詢過程。
此功能對於大型組織或具有大量分散式依賴的專案非常有用。
新的 --showConfig 標誌
TypeScript 編譯器 tsc 支援一個新的標誌 --showConfig。當執行 tsc --showConfig 時,TypeScript 將計算出有效的 tsconfig.json(在計算完從 extends 欄位繼承的選項後)並將其打印出來。這對於診斷一般配置問題非常有用。
JavaScript 中的 Object.defineProperty 宣告
在 JavaScript 檔案(使用 allowJs)中編寫時,TypeScript 現在可以識別使用 Object.defineProperty 的宣告。這意味著當在 JavaScript 檔案中啟用型別檢查(透過開啟 checkJs 選項或在檔案頂部新增 // @ts-check 註釋)時,你將獲得更好的補全提示和更強的型別檢查。
js// @ts-checklet obj = {};Object.defineProperty(obj, "x", { value: "hello", writable: false });obj.x.toLowercase();// ~~~~~~~~~~~// error:// Property 'toLowercase' does not exist on type 'string'.// Did you mean 'toLowerCase'?obj.x = "world";// ~// error:// Cannot assign to 'x' because it is a read-only property.