TypeScript 4.0正式發(fā)布!現(xiàn)在是開始使用它的最佳時機
作者 | Daniel Rosenwasser?譯者 | 王強?策劃 | 李俊辰
如果你還不熟悉 TypeScript,這里簡單介紹一下:它是一種在 JavaScript 之上通過添加靜態(tài)類型語法來構(gòu)建的語言。它的基本理念是,記下值的類型以及它們的使用位置后,可以使用 TypeScript 對代碼進行類型檢查,并在運行代碼之前(甚至在保存文件之前)告訴你代碼錯誤的相關(guān)信息。然后,你可以使用 TypeScript 編譯器從代碼中剝離類型,并為你提供可在任何地方運行的簡潔易讀的 JavaScript 代碼。除了類型檢查之外,TypeScript 還使用靜態(tài)類型來支持強大的編輯器工具,例如自動完成、代碼導航、重構(gòu)等。實際上,如果你在 Visual Studio Code 或 Visual Studio 這樣的編輯器中使用過 JavaScript,那么你已經(jīng)用上了類型和 TypeScript 帶來的體驗。可以在我們的網(wǎng)站上了解更多相關(guān)信息。
https://www.typescriptlang.org/
TypeScript 4.0 沒有引入特別重大的更改。實際上,如果你剛剛開始接觸這種語言,那么現(xiàn)在是開始使用它的最佳時機。它的社區(qū)已經(jīng)成熟完善,并在不斷發(fā)展,擁有可運行的代碼和很棒的新資源可供學習。還有一件事情:盡管我們?yōu)?4.0 引入了那么多好東西,但你實際上只需要了解 TypeScript 的基礎(chǔ)知識就可以開始生產(chǎn)應(yīng)用了!
npm install?-D typescript你還可以通過以下方式獲得編輯器支持:
下載 Visual Studio 2019/2017:
https://marketplace.visualstudio.com/items?itemName=TypeScriptTeam.TypeScript-40
安裝 Visual Studio Code 的內(nèi)部版本,或按照以下說明使用較新版本的 TypeScript。
https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-newer-typescript-versions
TypeScript 是當今許多人的 JavaScript 技術(shù)棧的核心部分。在 npm 上,TypeScript 在 7 月首次實現(xiàn)了超過 5000 萬的月下載量!盡管我們知道它總有增長和改進的余地,但很明顯,大多數(shù)使用 TypeScript 編碼的開發(fā)人員確實很喜歡它。StackOverflow 的最新開發(fā)人員調(diào)查將 TypeScript 列為第二受歡迎的語言。在最新的 JS 現(xiàn)狀調(diào)查中,使用 TypeScript 的開發(fā)人員中有大約 89% 表示會再次使用它。
值得一提的是我們走到今天所走過的旅程。在之前的兩個主要版本中,我們回顧了多年來閃耀的一些亮點。對于 TypeScript 4.0,我們將保持這種傳統(tǒng)。
從 3.0 版本向前看,可以看到許多令人眼花繚亂的更改,但是 TypeScript 3.0 本身就產(chǎn)生了很大的沖擊。統(tǒng)一元組類型和參數(shù)列表是當時的一大亮點,可在函數(shù)上啟用大量已有的 JavaScript 模式。這個發(fā)行版還提供了項目參考,以幫助擴展、組織和共享代碼庫。3.0 版的一個產(chǎn)生重大影響的小更改是對 any 引入了類型安全的替代方法,稱為 unknown。
TypeScript 3.1 擴展了映射類型的功能以處理元組和數(shù)組類型,并極大簡化了將屬性附加到函數(shù)的過程,而無需使用 TypeScript 專屬的運行時功能(已停用)。
TypeScript 3.2 允許對象在泛型類型上傳播,并通過嚴格類型化 bind、call 和 apply,利用 3.0 的功能更好地建模函數(shù)的元編程。TypeScript 3.3 更多關(guān)注穩(wěn)定性,但也改進了聯(lián)合類型方法,并在 --build 模式下添加了文件增量式構(gòu)建。
在 3.4 版本中,我們進一步支持函數(shù)式模式,更好地支持不可變數(shù)據(jù)結(jié)構(gòu),并改進了對高階泛型函數(shù)的推斷。這個發(fā)行版的一大改進是引入了 --incremental 標志,該方法避免了在每次 TypeScript 運行時完全重建,從而加快了編譯和類型檢查的速度。
在 TypeScript 3.5 和 3.6 中加強了一些類型系統(tǒng)規(guī)則,還帶來了更智能的兼容性檢查規(guī)則。
TypeScript 3.7 是一個非常值得關(guān)注的版本,因為它很好地結(jié)合了許多新的類型系統(tǒng)特性與 ECMAScript 特性。在類型系統(tǒng)方面,我們加入了遞歸類型別名引用和對斷言樣式函數(shù)的支持,這兩者都是獨特的類型系統(tǒng)特性。從 JavaScript 方面來看,該版本帶來了可選鏈和空值合并功能,這是 TypeScript 和 JavaScript 用戶最期待的兩項功能。
最近,3.8 和 3.9 帶來了僅類型的導入 / 導出,以及許多 ECMAScript 特性,例如私有字段、模塊中的頂級 await 和新的 export* 語法。這些版本還帶來了性能和可伸縮性優(yōu)化。
我們還沒有提到關(guān)于語言服務(wù)、基礎(chǔ)架構(gòu)、網(wǎng)站和其他核心項目中的那些工作,這些工作對于 TypeScript 的體驗非常關(guān)鍵。核心團隊的項目之外,我們在生態(tài)系統(tǒng)中還有非常出色的貢獻者社區(qū),他們推動了體驗的不斷改進,并通過 DefinitelyTyped 甚至 TypeScript 本身提供了幫助。在 2012 年剛開始時,DefinitelyTyped 僅有 80 個拉取請求。在 2019 年,它有超過 8300 個拉取請求,非常震撼人心。這些貢獻是 TypeScript 體驗的基礎(chǔ),這樣一個繁忙而熱情的社區(qū)在不斷改善我們的生態(tài)系統(tǒng),并推動我們不斷改進,我們對此表示感謝。
function?concat(arr1, arr2)?{
????return?[...arr1, ...arr2];
}function?tail(arg)?{
????const?[_, ...result] = arg;
????return?result
}function?concat<>(arr1: [], arr2: []): [A];
function?concat<A>(arr1: [A], arr2: []): [A];
function?concat<A, B>(arr1: [A, B], arr2: []): [A, B];
function?concat<A, B, C>(arr1: [A, B, C], arr2: []): [A, B, C];
function?concat<A, B, C, D>(arr1: [A, B, C, D], arr2: []): [A, B, C, D];
function?concat<A, B, C, D, E>(arr1: [A, B, C, D, E], arr2: []): [A, B, C, D, E];
function?concat<A, B, C, D, E, F>(arr1: [A, B, C, D, E, F], arr2: []): [A, B, C, D, E, F];)function?concat<A2>(arr1: [], arr2: [A2]): [A2];
function?concat<A1, A2>(arr1: [A1], arr2: [A2]): [A1, A2];
function?concat<A1, B1, A2>(arr1: [A1, B1], arr2: [A2]): [A1, B1, A2];
function?concat<A1, B1, C1, A2>(arr1: [A1, B1, C1], arr2: [A2]): [A1, B1, C1, A2];
function?concat<A1, B1, C1, D1, A2>(arr1: [A1, B1, C1, D1], arr2: [A2]): [A1, B1, C1, D1, A2];
function?concat<A1, B1, C1, D1, E1, A2>(arr1: [A1, B1, C1, D1, E1], arr2: [A2]): [A1, B1, C1, D1, E1, A2];
function?concat<A1, B1, C1, D1, E1, F1, A2>(arr1: [A1, B1, C1, D1, E1, F1], arr2: [A2]): [A1, B1, C1, D1, E1, F1, A2];function?concat<T, U>(arr1: T[], arr2, U[]): Array<T?| U>;但在使用元組時,這個簽名不會包含輸入長度或元素順序的任何信息。TypeScript 4.0 帶來了兩個基礎(chǔ)更改,并在推斷方面進行了改進,從而可以類型化這些內(nèi)容。
第一個更改是元組類型語法中的 spread 現(xiàn)在可以泛型。這意味著即使我們不知道要操作的實際類型,也可以表示對元組和數(shù)組的高階操作。在這些元組類型中實例化泛型 spread(或用真實類型替換)時,它們可以產(chǎn)生其他數(shù)組和元組類型集。
function?tail<T?extends?any[]>(arr: readonly [any, ...T]) {
????const?[_ignored, ...rest] = arr;
????return?rest;
}
const?myTuple = [1, 2, 3, 4] as?const;
const?myArray = ["hello", "world"];
// type [2, 3, 4]
const?r1 = tail(myTuple);
// type [2, 3, ...string[]]
const?r2 = tail([...myTuple, ...myArray] as?const);type?Strings = [string, string];
type?Numbers = [number, number];
// [string, string, number, number]
type?StrStrNumNum = [...Strings, ...Numbers];A rest element must be?last?in a?tuple type.但是現(xiàn)在這種限制取消了。
type?Strings = [string, string];
type?Numbers = number[]
// [string, string, ...Array]
type?Unbounded = [...Strings, ...Numbers, boolean];type?Arr = readonly any[];
function?concat<T?extends?Arr, U?extends?Arr>(arr1: T, arr2: U): [...T, ...U] {
????return?[...arr1, ...arr2];
}盡管一個簽名仍然有些冗長,但它畢竟只有一個,只需寫一次,并且在所有數(shù)組和元組上都具有可預測的行為。
function?partialCall(f, ...headArgs) {
????return?(...tailArgs) =>?f(...headArgs, ...tailArgs)
}type Arr = readonly unknown[];
function?partialCall<T?extends?Arr, U?extends?Arr, R>(f: (...args: [...T, ...U])?=> R, ...headArgs: T)?{
????return?(...b: U) => f(...headArgs, ...b)
}const?foo = (x: string, y: number, z: boolean) =>?{}
// This doesn't work because we're feeding in the wrong type for 'x'.
const?f1 = partialCall(foo, 100);
// ~~~
// error! Argument of type 'number' is not assignable to parameter of type 'string'.
// This doesn't work because we're passing in too many arguments.
const?f2 = partialCall(foo, "hello", 100, true, "oops")
// ~~~~~~
// error! Expected 4 arguments, but got 5.
// This works! It has the type '(y: number, z: boolean) => void'
const?f3 = partialCall(foo, "hello");
// What can we do with f3 now?
f3(123, true); // works!
f3();
// error! Expected 2 arguments, but got 0.
f3(123, "hello");
// ~~~~~~~
// error! Argument of type '"hello"' is not assignable to parameter of type 'boolean'.可變元組類型創(chuàng)造了許多新模式,尤其是在函數(shù)組合方面。我們希望利用它來改善對 JavaScript 內(nèi)置的 bind 方法的類型檢查。此外還有其他一些推斷改進和模式,想了解更多信息,可以查看可變元組的拉取請求。
https://github.com/microsoft/TypeScript/pull/39094
改善元組類型和參數(shù)列表的體驗很重要,因為它使我們能夠圍繞常見的 JavaScript 習慣用法進行強類型驗證——實際上只是對參數(shù)列表進行切片和切塊,并將它們傳遞給其他函數(shù)。對 rest 參數(shù)使用元組類型是其中的關(guān)鍵。
function?foo(...args: [string, number]): void?{
????// ...
}function?foo(arg0: string, arg1: number): void?{
????// ...
}foo("hello", 42); // works
foo("hello", 42, true); // error
foo("hello"); // errortype Range = [start: number, end: number];type?Foo = [first: number, second?: string, ...rest: any[]];type?Bar = [first: string, number];
// ~~~~~~
// error! Tuple members must all have names or all not have names.function?foo(x: [first: string, second: number]) {
????// ...
????// note:?we didn't need to name these 'first' and 'second'
????let?[a, b] = x;
????// ...
}總的來說,當利用圍繞元組和參數(shù)列表的模式,并以類型安全的方式實現(xiàn)重載時,帶標記的元組非常方便好用。實際上,TypeScript 的編輯器支持會在可能的情況下將它們顯示為重載。

了解更多信息,請查看帶標記的元組元素的拉取請求。
https://github.com/microsoft/TypeScript/pull/38234
class?Square {
????// Previously: implicit any!
????// Now: inferred to `number`!
????area;
????sideLength;
????constructor(sideLength: number) {
????????this.sideLength = sideLength;
????????this.area = sideLength ** 2;
????}
}class?Square {
????sideLength;
????constructor(sideLength: number) {
????????if?(Math.random()) {
????????????this.sideLength = sideLength;
????????}
????}
????get?area() {
????????return?this.sideLength ** 2;
????????// ~~~~~~~~~~~~~~~
????????// error! Object is possibly 'undefined'.
????}
}class?Square {
????// definite assignment assertion
????// v
????sideLength!: number;
????// ^^^^^^^^
????// type annotation
????constructor(sideLength: number) {
????????this.initialize(sideLength)
????}
????initialize(sideLength: number) {
????????this.sideLength = sideLength;
????}
????get?area() {
????????return?this.sideLength ** 2;
????}
}更多信息請見拉取請求。
https://github.com/microsoft/TypeScript/pull/379200
// Addition
// a = a + b
a += b;
// Subtraction
// a = a - b
a -= b;
// Multiplication
// a = a * b
a *= b;
// Division
// a = a / b
a /= b;
// Exponentiation
// a = a ** b
a **= b;
// Left Bit Shift
// a = a << b
a <<= b;JavaScript 中有很多運算符都有對應(yīng)的賦值運算符!但是有三個值得注意的例外:邏輯和(&&),邏輯或(||)和空值合并(??)。
所以 TypeScript 4.0 支持了一個新的 ECMAScript 特性,添加了三個新的賦值運算符:&&=,||= 和 ??= 。
a?= a && b;
a?= a || b;
a?= a ?? b;// could be 'a ||= b'
if?(!a) {
????a = b;
}let?values: string[];
// Before
(values ?? (values = [])).push("hello");
// After
(values ??= []).push("hello");obj.prop ||= foo();
//?roughly equivalent to either of the following
obj.prop ||?(obj.prop = foo());
if?(!obj.prop) {
????obj.prop = foo();
}const?obj = {
????get?prop() {
????????console.log("getter has run");
????????// Replace me!
????????return?Math.random() < 0.5;
????},
????set?prop(_val: boolean) {
????????console.log("setter has run");
????}
};
function?foo() {
????console.log("right side evaluated");
????return?true;
}
console.log("This one always runs the setter");
obj.prop = obj.prop || foo();
console.log("This one *sometimes* runs the setter");
obj.prop ||= foo();有關(guān)更多細節(jié)可以查看拉取請求。
https://github.com/microsoft/TypeScript/pull/37727
你也可以查看 TC39 的提案存儲庫。
https://github.com/tc39/proposal-logical-assignment/
try?{
????// ...
}
catch?(x) {
????// x has type 'any' - have fun!
????console.log(x.message);
????console.log(x.toUpperCase());
????x++;
????x.yadda.yadda.yadda();
}try?{
????// ...
}
catch?(e: unknown) {
????// error!
????// Property 'toUpperCase' does not exist on type 'unknown'.
????console.log(e.toUpperCase());
????if?(typeof?e === "string") {
????????// works!
????????// We've narrowed 'e' down to the type 'string'.
????????console.log(e.toUpperCase());
????}
}盡管默認情況下 catch 變量的類型不會更改,但我們將來可能會考慮使用新的 --strict 模式標志,以便用戶選擇啟用此行為。同時,應(yīng)該可以編寫一個 lint 規(guī)則來強制 catch 變量具有如下顯式注解之一:: any 或: unknown。
有關(guān)更多信息,可以查看拉取請求。
https://github.com/microsoft/TypeScript/pull/39015
使用 JSX 時,fragment 是 JSX 元素的一種,允許我們返回多個子元素。當我們第一次在 TypeScript 中實現(xiàn) fragment 時,我們對其他庫如何利用它們并不了解。如今,大多數(shù)鼓勵使用 JSX 和支持 fragment 的庫都具有類似的 API 設(shè)計。
在 TypeScript 4.0 中,用戶可以通過新的 jsxFragmentFactory 選項來自定義 fragment 工廠。
{
??"compilerOptions": {
????"target": "esnext",
????"module": "commonjs",
????"jsx": "react",
????"jsxFactory": "h",
????"jsxFragmentFactory": "Fragment"
??}
}// Note:?these pragma comments need to be written
// with a JSDoc-style multiline syntax to take effect.
/** @jsx h */
/** @jsxFrag Fragment */
import?{ h, Fragment } from?"preact";
let?stuff = <>
????<div>Hellodiv>
>;// Note:?these pragma comments need to be written
// with a JSDoc-style multiline syntax to take effect.
/** @jsx h */
/** @jsxFrag Fragment */
import?{ h, Fragment } from?"preact";
let?stuff = h(Fragment, null,
????h("div", null, "Hello"));查看拉取請求以獲取更多信息。
https://github.com/microsoft/TypeScript/pull/38720
以前,使用 --noEmitOnError 標志時,當先前的編譯在 --incremental 下出現(xiàn)錯誤,編譯速度將非常緩慢。這是因為基于 --noEmitOnError 標志,上次編譯的任何信息都不會緩存在.tsbuildinfo 文件中。
TypeScript 4.0 對此進行了更改,從而在這些情況下極大地提高了速度,進而改進了 --build 模式的場景(這意味著同時有 --incremental 和 --noEmitOnError)。
有關(guān)詳細信息,請查看拉取請求。
https://github.com/microsoft/TypeScript/pull/38853
TypeScript 4.0 允許我們在利用 --incremental 編譯時使用 --noEmit 標志。以前不允許這樣做,因為 --incremental 需要發(fā)出.tsbuildinfo 文件。
有關(guān)詳細信息,請查看拉取請求。
https://github.com/microsoft/TypeScript/pull/39122
TypeScript 編譯器不僅可以為大多數(shù)主流編輯器提供較好的 TS 編輯體驗,還可以改進 Visual Studio 系列編輯器的 JavaScript 開發(fā)體驗。
根據(jù)你使用的編輯器,在編輯器中使用新的 TypeScript/JavaScript 功能時會有區(qū)別:
Visual Studio Code 支持選擇不同版本的 TypeScript。另外,還有 JavaScript/TypeScript Nightly Extension 來緊跟最新版本(通常非常穩(wěn)定)。
Visual Studio 2017/2019 有上面的 SDK 安裝程序和 MSBuild 安裝。
更多信息見 TS 編輯器支持列表。
https://github.com/Microsoft/TypeScript/wiki/TypeScript-Editor-Support
可選鏈是一項新功能,受到了廣泛的歡迎。TypeScript 4.0 在轉(zhuǎn)換常見模式時可以利用可選鏈和空值合并的優(yōu)勢!

我們認為這種重構(gòu)應(yīng)該能捕獲大多數(shù)用例的意圖,尤其是當 TypeScript 對你的類型有更精確的了解時。
有關(guān)詳細信息,請查看拉取請求。
https://github.com/microsoft/TypeScript/pull/39135
現(xiàn)在,TypeScript 的編輯支持可以識別聲明中是否帶有/** @deprecated*/ JSDoc 注釋。該信息顯示在自動完成列表中,并作為編輯器可以特別處理的建議診斷。在像 VSCode 這樣的編輯器中,deprecated 的值通常顯示為刪除線樣式。

有關(guān)詳細信息,查看拉取請求。
https://github.com/microsoft/TypeScript/pull/38523
很多用戶抱怨啟動時間緩慢,尤其是在大型項目中。具體來說,罪魁禍首通常是一個稱為項目加載的過程,該過程與我們編譯器的程序構(gòu)建步驟大致相同。這一過程從一組初始文件開始,解析它們、解析它們的依賴、再解析那些依賴,解析那些依賴的依賴,等等,最后需要花費很長時間。項目越大,啟動延遲可能會越長。
所以我們一直在努力為開發(fā)人員提供一種新的模式,在獲得完整的語言服務(wù)體驗之前提供部分體驗。這里的核心思想是,編輯者可以運行僅具有單個文件視圖的輕量級部分服務(wù)器。
這種新模式可以將 TypeScript 在代碼庫上開始交互之前的準備時間從 20 秒到 1 分鐘縮短到只有幾秒鐘。比如說,在較大的代碼庫上重啟編輯器時,TS 3.9 版沒法立即提供自動完成和快速信息;另一方面,TS 4.0 可以立即提供完整的編輯體驗,同時在后臺加載整個項目。
當前,唯一支持此模式的編輯器是 Visual Studio Code,但 UX 和功能仍有改進的余地。我們列出了準備加入的改進,希望獲得更多反饋。
https://github.com/microsoft/TypeScript/issues/39035
有關(guān)更多信息,你可以查看原始提案,拉取請求,以及后續(xù)的 meta 問題。
https://github.com/microsoft/TypeScript/issues/37713
自動導入是一個了不起的功能。但是,自動導入在用 TypeScript 編寫的包上不起作用——也就是說,我們得在項目的其他位置至少寫了一個顯式導入。
為什么自動導入適用于 @types 軟件包,而不適用于使用自己類型的包呢?其實自動導入是通過檢查項目中已經(jīng)包含的軟件包來實現(xiàn)的。TypeScript 有一個怪癖,可以自動包括 node_modules/@types 中的所有包,而忽略其他包;但爬取所有 node_modules 包的開銷可能會很昂貴。
當你嘗試自動導入剛剛安裝但尚未使用的內(nèi)容時,這些都會導致糟糕的體驗。
TypeScript 4.0 現(xiàn)在可以包含你在 package.json 的 dependencies(和 peerDependencies)字段中列出的包。這些包中的信息僅用于改進自動導入,不會更改類型檢查等其他內(nèi)容。這樣就避免了遍歷 node_modules 目錄的成本,使我們可以為所有帶類型的依賴項提供自動導入。
當你的 package.json 列出了超過十項尚未導入的類型化依賴項時,這個功能會自動禁用,以避免緩慢的項目加載過程。要強制開啟它或完全禁用它,你可以配置編輯器。在 Visual Studio Code 中是"Include Package JSON Auto Imports"設(shè)置(或 typescript.preferences.includePackageJsonAutoImports)。

有關(guān)詳細信息,可以查看提案問題以及拉取請求。
https://github.com/microsoft/TypeScript/issues/37812
TypeScript 網(wǎng)站最近被徹底重寫了!

詳細信息可以參考之前的文章:
《TypeScript 新版網(wǎng)站上線:帶來了新的導航機制》
我們的 lib.d.ts 聲明已更改,具體來說是 DOM 的類型已更改。主要是刪除了 document.origin,它僅在 IE 的舊版本中有效,而 Safari MDN 建議改用 self.origin。
class?Base?{
????get foo() {
????????return?100;
????}
????set foo() {
????????//?...
????}
}
class?Derived?extends?Base?{
????foo = 10;
//??~~~
//?error!
//?'foo'?is?defined as?an accessor in?class?'Base',
//?but is?overridden here in?'Derived'?as?an instance property.
}
class?Base?{
????prop = 10;
}
class?Derived?extends?Base?{
????get prop()?{
????// ~~~~
????// error!
????// 'prop' is defined as a property in class 'Base', but is overridden here in 'Derived' as an accessor.
????????return?100;
????}
}有關(guān)詳細信息,查看拉取請求。
https://github.com/microsoft/TypeScript/pull/37894
interface?Thing {
????prop: string;
}
function?f(x: Thing) {
????delete?x.prop;
????// ~~~~~~
????// error! The operand of a 'delete' operator must be optional.
}關(guān)于更多信息,查看拉取請求。
https://github.com/microsoft/TypeScript/pull/37921
如今,TypeScript 提供了一組用于生成 AST 節(jié)點的“工廠”函數(shù)。但是,TypeScript 4.0 提供了新的 node 工廠 API。因此 TypeScript 4.0 決定棄用使用這些舊函數(shù),推薦改用新函數(shù)。
有關(guān)更多信息,請查看拉取請求。
https://github.com/microsoft/TypeScript/pull/35282
TypeScript 4.1 的迭代計劃已經(jīng)上線了,你可以大致了解一下。
https://github.com/microsoft/TypeScript/issues/40124
同時,你可以在工作區(qū)或編輯器中使用 nightly 構(gòu)建來預覽 4.1 中添加的新特性。無論你是在使用 TypeScript 4.0 還是下一版本,我們都希望聽到你的反饋!可以通過 Twitter 聯(lián)系我們,或在 GitHub 上發(fā)起問題。
我們再一次為社區(qū)所做的一切工作和奉獻精神深表感謝。我們希望讓 TypeScript 和 JavaScript 的編碼體驗成為你應(yīng)得的純粹樂趣。為此,我們需要改善語言和編輯體驗、提升性能、迭代我們的用戶體驗、降低入門和學習的門檻等等。
非常感謝大家,請享用 4.0 版本吧,編程愉快!
https://devblogs.microsoft.com/typescript/announcing-typescript-4-0/
??愛心三連擊
點分享 點點贊 點在看



