TypeScript 3.4

使用 --incremental 標誌實現更快的後續構建

TypeScript 3.4 引入了一個名為 incremental 的新標誌,它告訴 TypeScript 儲存上次編譯時的專案圖資訊。下次使用 incremental 呼叫 TypeScript 時,它將利用這些資訊以成本最低的方式對專案中的更改進行型別檢查和輸出。

// tsconfig.json
{
"": true,
"": "./lib"
},
"": ["./src"]
}

在預設設定下,當我們執行 tsc 時,TypeScript 會在輸出目錄 (./lib) 中尋找名為 .tsbuildinfo 的檔案。如果 ./lib/.tsbuildinfo 不存在,它將被生成。但如果它存在,tsc 將嘗試使用該檔案進行增量型別檢查並更新輸出檔案。

這些 .tsbuildinfo 檔案可以安全刪除,並且對我們執行時的程式碼沒有任何影響——它們純粹用於加快編譯速度。我們還可以透過 tsBuildInfoFile 選項為它們隨意命名,並放置在任何我們想要的位置。

// front-end.tsconfig.json
{
"": true,
"": "./buildcache/front-end",
"": "./lib"
},
"": ["./src"]
}

複合專案 (Composite projects)

複合專案(將 composite 設定為 truetsconfig.json)的部分意圖是不同專案之間的引用可以增量構建。因此,複合專案將始終生成 .tsbuildinfo 檔案。

outFile

當使用 outFile 時,構建資訊檔案的名稱將基於輸出檔案的名稱。例如,如果我們的輸出 JavaScript 檔案是 ./output/foo.js,那麼在使用 incremental 標誌時,TypeScript 將生成 ./output/foo.tsbuildinfo 檔案。如上所述,這可以透過 tsBuildInfoFile 選項進行控制。

泛型函式的高階型別推斷

TypeScript 3.4 現在可以產生泛型函式型別,即使從其他泛型函式推斷產生自由型別變數時也是如此。這意味著許多函式組合模式在 3.4 中能夠更好地工作。

具體來說,讓我們建立一些動力並考慮以下 compose 函式:

ts
function compose<A, B, C>(f: (arg: A) => B, g: (arg: B) => C): (arg: A) => C {
return (x) => g(f(x));
}

compose 接收另外兩個函式

  • f,它接收某個引數(型別為 A)並返回一個型別為 B 的值
  • g,它接收一個型別為 Bf 返回的型別)的引數,並返回一個型別為 C 的值

然後 compose 返回一個函式,該函式將其引數透過 f 處理,然後透過 g 處理。

在呼叫此函式時,TypeScript 將嘗試透過一個稱為型別引數推斷的過程找出 ABC 的型別。此推斷過程通常非常有效。

ts
interface Person {
name: string;
age: number;
}
function getDisplayName(p: Person) {
return p.name.toLowerCase();
}
function getLength(s: string) {
return s.length;
}
// has type '(p: Person) => number'
const getDisplayNameLength = compose(getDisplayName, getLength);
// works and returns the type 'number'
getDisplayNameLength({ name: "Person McPersonface", age: 42 });

此處的推斷過程相當直接,因為 getDisplayNamegetLength 使用了可以輕鬆引用的型別。然而,在 TypeScript 3.3 及更早版本中,當將其他泛型函式傳遞給像 compose 這樣的泛型函式時,效果並不理想。

ts
interface Box<T> {
value: T;
}
function makeArray<T>(x: T): T[] {
return [x];
}
function makeBox<U>(value: U): Box<U> {
return { value };
}
// has type '(arg: {}) => Box<{}[]>'
const makeBoxedArray = compose(makeArray, makeBox);
makeBoxedArray("hello!").value[0].toUpperCase();
// ~~~~~~~~~~~
// error: Property 'toUpperCase' does not exist on type '{}'.

在舊版本中,當從其他型別變數(如 TU)進行推斷時,TypeScript 會推斷為空物件型別 ({})。

在 TypeScript 3.4 的型別引數推斷過程中,對於呼叫返回函式型別的泛型函式,TypeScript 根據需要將型別引數從泛型函式引數傳播到結果函式型別上。

換句話說,TypeScript 3.4 不再產生該型別,而是產生

ts
(arg: {}) => Box<{}[]>

TypeScript 3.4 產生的型別

ts
<T>(arg: T) => Box<T[]>

請注意,T 已經從 makeArray 傳播到了結果型別的型別引數列表中。這意味著 compose 引數的泛型特性得以保留,我們的 makeBoxedArray 示例將能夠直接執行!

ts
interface Box<T> {
value: T;
}
function makeArray<T>(x: T): T[] {
return [x];
}
function makeBox<U>(value: U): Box<U> {
return { value };
}
// has type '<T>(arg: T) => Box<T[]>'
const makeBoxedArray = compose(makeArray, makeBox);
// works with no problem!
makeBoxedArray("hello!").value[0].toUpperCase();

有關更多詳細資訊,您可以閱讀原始變更詳情

ReadonlyArrayreadonly 元組的改進

TypeScript 3.4 使只讀陣列類型別的使用變得更加容易。

ReadonlyArray 的新語法

ReadonlyArray 型別描述了只能從中讀取的 Array。任何引用 ReadonlyArray 的變數都無法新增、刪除或替換陣列中的任何元素。

ts
function foo(arr: ReadonlyArray<string>) {
arr.slice(); // okay
arr.push("hello!"); // error!
}

雖然在不打算進行突變時使用 ReadonlyArray 代替 Array 是很好的做法,但由於陣列具有更友好的語法,這通常很麻煩。具體來說,number[]Array<number> 的簡寫,就像 Date[]Array<Date> 的簡寫一樣。

TypeScript 3.4 為 ReadonlyArray 引入了一種新語法,使用新的 readonly 修飾符用於陣列型別。

ts
function foo(arr: readonly string[]) {
arr.slice(); // okay
arr.push("hello!"); // error!
}

readonly 元組

TypeScript 3.4 還引入了對 readonly 元組的新支援。我們可以在任何元組型別前加上 readonly 關鍵字,使其成為 readonly 元組,就像我們現在使用陣列簡寫語法一樣。正如您所料,與可以寫入插槽的普通元組不同,readonly 元組僅允許從這些位置讀取。

ts
function foo(pair: readonly [string, string]) {
console.log(pair[0]); // okay
pair[1] = "hello!"; // error
}

與普通元組是擴充套件自 Array 的型別一樣——元素型別為 T1, T2, … Tn 的元組擴充套件自 Array< T1 | T2 | … Tn >——readonly 元組是擴充套件自 ReadonlyArray 的型別。因此,元素為 T1, T2, … Tnreadonly 元組擴充套件自 ReadonlyArray< T1 | T2 | … Tn >

readonly 對映型別修飾符和 readonly 陣列

在較早版本的 TypeScript 中,我們泛化了對映型別以在陣列類型別上進行不同的操作。這意味著像 Boxify 這樣的對映型別可以同時作用於陣列和元組。

ts
interface Box<T> {
value: T;
}
type Boxify<T> = {
[K in keyof T]: Box<T[K]>;
};
// { a: Box<string>, b: Box<number> }
type A = Boxify<{ a: string; b: number }>;
// Array<Box<number>>
type B = Boxify<number[]>;
// [Box<string>, Box<number>]
type C = Boxify<[string, boolean]>;

不幸的是,像 Readonly 工具型別這樣的對映型別對於陣列和元組型別實際上是無操作的 (no-ops)。

ts
// lib.d.ts
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// How code acted *before* TypeScript 3.4
// { readonly a: string, readonly b: number }
type A = Readonly<{ a: string; b: number }>;
// number[]
type B = Readonly<number[]>;
// [string, boolean]
type C = Readonly<[string, boolean]>;

在 TypeScript 3.4 中,對映型別中的 readonly 修飾符會自動將陣列類型別轉換為其對應的 readonly 對等物。

ts
// How code acts now *with* TypeScript 3.4
// { readonly a: string, readonly b: number }
type A = Readonly<{ a: string; b: number }>;
// readonly number[]
type B = Readonly<number[]>;
// readonly [string, boolean]
type C = Readonly<[string, boolean]>;

同樣,您可以編寫一個像 Writable 這樣的對映型別,它剝離 readonly 屬性,這將把 readonly 陣列容器轉換回其可變的等價物。

ts
type Writable<T> = {
-readonly [K in keyof T]: T[K];
};
// { a: string, b: number }
type A = Writable<{
readonly a: string;
readonly b: number;
}>;
// number[]
type B = Writable<readonly number[]>;
// [string, boolean]
type C = Writable<readonly [string, boolean]>;

注意事項

儘管看起來如此,readonly 型別修飾符只能用於陣列型別和元組型別的語法中。它不是通用的型別運算子。

ts
let err1: readonly Set<number>; // error!
let err2: readonly Array<boolean>; // error!
let okay: readonly boolean[]; // works fine

您可以在拉取請求中檢視更多詳細資訊

const 斷言

TypeScript 3.4 為字面量值引入了一種稱為 const 斷言的新結構。其語法是型別斷言,其中 const 代替了型別名稱(例如 123 as const)。當我們使用 const 斷言構造新的字面量表達式時,我們可以向語言發出訊號,即:

  • 該表示式中的任何字面量型別都不應被擴大(例如,不會從 "hello" 變為 string
  • 物件字面量獲得 readonly 屬性
  • 陣列字面量變為 readonly 元組
ts
// Type '"hello"'
let x = "hello" as const;
// Type 'readonly [10, 20]'
let y = [10, 20] as const;
// Type '{ readonly text: "hello" }'
let z = { text: "hello" } as const;

.tsx 檔案之外,也可以使用尖括號斷言語法。

ts
// Type '"hello"'
let x = <const>"hello";
// Type 'readonly [10, 20]'
let y = <const>[10, 20];
// Type '{ readonly text: "hello" }'
let z = <const>{ text: "hello" };

這一功能意味著,那些原本僅用於向編譯器暗示不可變性的型別往往可以省略。

ts
// Works with no types referenced or declared.
// We only needed a single const assertion.
function getShapes() {
let result = [
{ kind: "circle", radius: 100 },
{ kind: "square", sideLength: 50 },
] as const;
return result;
}
for (const shape of getShapes()) {
// Narrows perfectly!
if (shape.kind === "circle") {
console.log("Circle radius", shape.radius);
} else {
console.log("Square side length", shape.sideLength);
}
}

請注意,上述內容不需要任何型別註解。const 斷言允許 TypeScript 獲取表示式的最具體型別。

如果您選擇不使用 TypeScript 的 enum 結構,這甚至可以用於在純 JavaScript 程式碼中啟用類似 enum 的模式。

ts
export const Colors = {
red: "RED",
blue: "BLUE",
green: "GREEN",
} as const;
// or use an 'export default'
export default {
red: "RED",
blue: "BLUE",
green: "GREEN",
} as const;

注意事項

需要注意的一點是,const 斷言只能立即應用於簡單的字面量表達式。

ts
// Error! A 'const' assertion can only be applied
// to a string, number, boolean, array, or object literal.
let a = (Math.random() < 0.5 ? 0 : 1) as const;
let b = (60 * 60 * 1000) as const;
// Works!
let c = Math.random() < 0.5 ? (0 as const) : (1 as const);
let d = 3_600_000 as const;

需要記住的另一件事是,const 上下文不會立即將表示式轉換為完全不可變的。

ts
let arr = [1, 2, 3, 4];
let foo = {
name: "foo",
contents: arr,
} as const;
foo.name = "bar"; // error!
foo.contents = []; // error!
foo.contents.push(5); // ...works!

有關更多詳細資訊,您可以檢視相關的拉取請求

globalThis 的型別檢查

TypeScript 3.4 引入了對 ECMAScript 新增的 globalThis 的型別檢查支援——這是一個全域性變數,引用全域性作用域。與上述解決方案不同,globalThis 提供了一種訪問全域性作用域的標準方式,可以在不同環境中使用。

ts
// in a global file:
var abc = 100;
// Refers to 'abc' from above.
globalThis.abc = 200;

請注意,用 letconst 宣告的全域性變數不會顯示在 globalThis 上。

ts
let answer = 42;
// error! Property 'answer' does not exist on 'typeof globalThis'.
globalThis.answer = 333333;

還需要注意的是,TypeScript 在編譯為舊版本 ECMAScript 時,不會轉換對 globalThis 的引用。因此,除非您針對的是 evergreen 瀏覽器(已經支援 globalThis),否則您可能需要使用適當的 polyfill

有關實現的更多詳細資訊,請參閱該功能的拉取請求

TypeScript 文件是一個開源專案。請透過傳送 Pull Request 幫助我們改進這些頁面 ❤

此頁面的貢獻者
DRDaniel Rosenwasser (51)
OTOrta Therox (14)
SShun 🎴 (1)
JBJack Bates (1)
MUMasato Urai (1)
4+

最後更新:2026 年 3 月 27 日