更嚴格的生成器(Stricter Generators)
TypeScript 3.6 為迭代器和生成器函式引入了更嚴格的檢查。在舊版本中,生成器的使用者無法區分一個值是從生成器中 yield(產出)的還是 return(返回)的。
tsfunction* foo() {if (Math.random() < 0.5) yield 100;return "Finished!";}let iter = foo();let curr = iter.next();if (curr.done) {// TypeScript 3.5 and prior thought this was a 'string | number'.// It should know it's 'string' since 'done' was 'true'!curr.value;}
此外,生成器僅假定 yield 的型別始終為 any。
tsfunction* bar() {let x: { hello(): void } = yield;x.hello();}let iter = bar();iter.next();iter.next(123); // oops! runtime error!
在 TypeScript 3.6 中,檢查器現在知道在我們的第一個示例中 curr.value 的正確型別應該是 string,並且會對最後一個示例中的 next() 呼叫正確報錯。這得益於 Iterator 和 IteratorResult 型別宣告的一些更改(引入了幾個新的型別引數),以及 TypeScript 用於表示生成器的一個名為 Generator 的新型別。
Iterator 型別現在允許使用者指定 yield 的型別、返回的型別以及 next 可以接受的型別。
tsinterface Iterator<T, TReturn = any, TNext = undefined> {// Takes either 0 or 1 arguments - doesn't accept 'undefined'next(...args: [] | [TNext]): IteratorResult<T, TReturn>;return?(value?: TReturn): IteratorResult<T, TReturn>;throw?(e?: any): IteratorResult<T, TReturn>;}
基於這些工作,新的 Generator 型別是一個同時具有 return 和 throw 方法且可迭代的 Iterator。
tsinterface Generator<T = unknown, TReturn = any, TNext = unknown>extends Iterator<T, TReturn, TNext> {next(...args: [] | [TNext]): IteratorResult<T, TReturn>;return(value: TReturn): IteratorResult<T, TReturn>;throw(e: any): IteratorResult<T, TReturn>;[Symbol.iterator](): Generator<T, TReturn, TNext>;}
為了允許區分返回值和 yield 的值,TypeScript 3.6 將 IteratorResult 型別轉換為可辨識聯合型別(discriminated union type)。
tstype IteratorResult<T, TReturn = any> =| IteratorYieldResult<T>| IteratorReturnResult<TReturn>;interface IteratorYieldResult<TYield> {done?: false;value: TYield;}interface IteratorReturnResult<TReturn> {done: true;value: TReturn;}
簡而言之,這意味著當直接處理來自迭代器的值時,你將能夠適當地收窄這些值。
為了正確表示透過呼叫 next() 可以傳遞給生成器的型別,TypeScript 3.6 還推斷了生成器函式體中 yield 的某些用法。
tsfunction* foo() {let x: string = yield;console.log(x.toUpperCase());}let x = foo();x.next(); // first call to 'next' is always ignoredx.next(42); // error! 'number' is not assignable to 'string'
如果你更喜歡顯式指定,也可以使用顯式返回型別來強制執行可以從 yield 表示式返回、產出和評估的值的型別。在下文中,next() 只能使用 boolean 值呼叫;根據 done 的值,value 可以是 string 或 number。
ts/*** - yields numbers* - returns strings* - can be passed in booleans*/function* counter(): Generator<number, string, boolean> {let i = 0;while (true) {if (yield i++) {break;}}return "done!";}var iter = counter();var curr = iter.next();while (!curr.done) {console.log(curr.value);curr = iter.next(curr.value === 5);}console.log(curr.value.toUpperCase());// prints://// 0// 1// 2// 3// 4// 5// DONE!
有關更改的更多詳細資訊,請參閱此處的 pull request。
更準確的陣列展開(Array Spread)
在 ES2015 之前的目標版本中,對於 for/of 迴圈和陣列展開等結構的最高保真度編譯可能會顯得比較重。因此,TypeScript 預設使用一種僅支援陣列型別的簡化編譯方式,並使用 downlevelIteration 標誌來支援在其他型別上進行迭代。不帶 downlevelIteration 的寬鬆預設值效果尚可;但是,在某些常見情況下,陣列展開的轉換存在可觀察到的差異。例如,以下包含展開操作的陣列
ts[...Array(5)];
可以重寫為以下陣列字面量
js[undefined, undefined, undefined, undefined, undefined];
然而,TypeScript 會將原始程式碼轉換為如下程式碼
tsArray(5).slice();
這略有不同。Array(5) 生成一個長度為 5 的陣列,但沒有任何定義的屬性槽位(property slots)。
TypeScript 3.6 引入了一個新的 __spreadArrays 輔助函式,以便在 downlevelIteration 之外的舊目標中準確模擬 ECMAScript 2015 中的行為。__spreadArrays 也可在 tslib 中使用。
有關更多資訊,請參閱相關的 pull request。
改進 Promises 的使用者體驗
TypeScript 3.6 為 Promise 被錯誤處理的情況引入了一些改進。
例如,在將 Promise 內容傳遞給其他函式之前忘記 .then() 或 await 它非常常見。TypeScript 的錯誤訊息現在是專門定製的,會告知使用者或許應該考慮使用 await 關鍵字。
tsinterface User {name: string;age: number;location: string;}declare function getUserData(): Promise<User>;declare function displayUser(user: User): void;async function f() {displayUser(getUserData());// ~~~~~~~~~~~~~// Argument of type 'Promise<User>' is not assignable to parameter of type 'User'.// ...// Did you forget to use 'await'?}
在 await 或 .then() 一個 Promise 之前嘗試訪問其方法也很常見。這是我們可以做得更好的眾多例子之一。
tsasync function getCuteAnimals() {fetch("https://reddit.com/r/aww.json").json();// ~~~~// Property 'json' does not exist on type 'Promise<Response>'.//// Did you forget to use 'await'?}
有關詳細資訊,請參閱原始 issue 以及連結回該 issue 的 pull requests。
更好的識別符號 Unicode 支援
當編譯到 ES2015 及更高版本的目標時,TypeScript 3.6 包含對識別符號中 Unicode 字元的更好支援。
tsconst 𝓱𝓮𝓵𝓵𝓸 = "world"; // previously disallowed, now allowed in '--target es2015'
SystemJS 中的 import.meta 支援
當你的 module 目標設定為 system 時,TypeScript 3.6 支援將 import.meta 轉換為 context.meta。
ts// This module:console.log(import.meta.url);// gets turned into the following:System.register([], function (exports, context) {return {setters: [],execute: function () {console.log(context.meta.url);},};});
Ambient 上下文中允許使用 get 和 set 訪問器
在 TypeScript 的先前版本中,語言不允許在 Ambient 上下文(如 declare 類或通常在 .d.ts 檔案中)中使用 get 和 set 訪問器。其理由是,就讀寫這些屬性而言,訪問器與屬性並沒有什麼區別;但是,由於 ECMAScript 的類欄位提案的行為可能與現有 TypeScript 版本中的行為不同,我們意識到我們需要一種方式來傳達這種差異,以便在子類中提供適當的錯誤提示。
因此,使用者可以在 TypeScript 3.6 的 Ambient 上下文中編寫 getter 和 setter。
tsdeclare class Foo {// Allowed in 3.6+.get x(): number;set x(val: number);}
在 TypeScript 3.7 中,編譯器本身將利用此功能,以便生成的 .d.ts 檔案也能輸出 get/set 訪問器。
Ambient 類和函式可以合併
在舊版本的 TypeScript 中,在任何情況下合併類和函式都是錯誤的。現在,Ambient 類和函式(帶有 declare 修飾符的類/函式,或位於 .d.ts 檔案中)可以合併。這意味著現在你可以這樣寫
tsexport declare function Point2D(x: number, y: number): Point2D;export declare class Point2D {x: number;y: number;constructor(x: number, y: number);}
而不是必須使用
tsexport interface Point2D {x: number;y: number;}export declare var Point2D: {(x: number, y: number): Point2D;new (x: number, y: number): Point2D;};
這種做法的一個優點是,可以輕鬆表達可呼叫的建構函式模式,同時允許名稱空間與這些宣告合併(因為 var 宣告不能與 namespace 合併)。
在 TypeScript 3.7 中,編譯器將利用此功能,以便從 .js 檔案生成的 .d.ts 檔案可以適當地捕獲類函式的“可呼叫性”和“可構造性”。
有關更多詳細資訊,請參閱 GitHub 上的原始 PR。
支援 --build 和 --incremental 的 API
TypeScript 3.0 引入了對引用其他專案並透過 --build 標誌進行增量構建的支援。此外,TypeScript 3.4 引入了 incremental 標誌,用於儲存有關先前編譯的資訊,以便僅重新構建某些檔案。這些標誌對於更靈活地構建專案和加快構建速度非常有用。遺憾的是,使用這些標誌在 Gulp 和 Webpack 等第三方構建工具中無法正常工作。TypeScript 3.6 現在公開了兩組 API 來操作專案引用和增量程式構建。
為了建立 incremental 構建,使用者可以利用 createIncrementalProgram 和 createIncrementalCompilerHost API。使用者還可以使用新公開的 readBuilderProgram 函式從該 API 生成的 .tsbuildinfo 檔案中重新恢復舊程式例項。該函式僅用於建立新程式(即,你無法修改返回的例項 - 它僅用於在其他 create*Program 函式中作為 oldProgram 引數)。
為了利用專案引用,公開了一個新的 createSolutionBuilder 函式,它返回一個新型別 SolutionBuilder 的例項。
有關這些 API 的更多詳細資訊,你可以檢視原始 pull request。
感知分號的程式碼編輯
Visual Studio 和 Visual Studio Code 等編輯器可以自動應用快速修復、重構以及其他轉換(例如自動從其他模組匯入值)。這些轉換由 TypeScript 提供支援,而舊版本的 TypeScript 會無條件地在每個語句末尾新增分號;遺憾的是,這與許多使用者的風格指南不一致,許多使用者對編輯器自動插入分號感到不滿。
TypeScript 現在足夠智慧,可以在應用此類編輯時檢測你的檔案是否使用分號。如果你的檔案通常缺少分號,TypeScript 就不會新增分號。
有關更多詳細資訊,請參閱相應的 pull request。
更智慧的自動匯入語法
JavaScript 有許多不同的模組語法或約定:ECMAScript 標準中的語法、Node 已經支援的語法 (CommonJS)、AMD、System.js 等等!在大多數情況下,TypeScript 預設使用 ECMAScript 模組語法進行自動匯入,這在某些具有不同編譯器設定的 TypeScript 專案中,或者在帶有純 JavaScript 和 require 呼叫的 Node 專案中通常是不合適的。
TypeScript 3.6 現在更智慧了,它會在決定如何自動匯入其他模組之前檢視你現有的匯入。你可以在 這裡檢視原始 pull request 中的更多詳細資訊。
新的 TypeScript Playground
TypeScript Playground 迎來了亟需的更新,並具備了便捷的新功能!新的 Playground 在很大程度上是 Artem Tyurin 的 TypeScript Playground 的分支,社群成員一直在越來越多地使用它。我們非常感謝 Artem 在此提供的幫助!
新的 Playground 現在支援許多新選項,包括
target選項(允許使用者從es5切換到es3、es2015、esnext等)- 所有的嚴格模式標誌(包括僅僅
strict) - 支援純 JavaScript 檔案(使用
allowJS和可選的checkJs)
這些選項在共享 Playground 示例連結時也會持久保留,從而使使用者能更可靠地共享示例,而不必告訴接收者“哦,別忘了開啟 noImplicitAny 選項!”。
在不久的將來,我們將重新整理 Playground 示例,增加 JSX 支援,並完善自動型別獲取,這意味著你將能夠在 Playground 上獲得與在個人編輯器中相同的體驗。
隨著我們改進 Playground 和網站,我們歡迎在 GitHub 上提出反饋和 pull requests!