常量命名屬性
TypeScript 2.7 增加了對在型別上宣告常量命名屬性的支援,包括 ECMAScript 符號(Symbols)。
示例
ts// Libexport const SERIALIZE = Symbol("serialize-method-key");export interface Serializable {[SERIALIZE](obj: {}): string;}
ts// consumerimport { SERIALIZE, Serializable } from "lib";class JSONSerializableItem implements Serializable {[SERIALIZE](obj: {}) {return JSON.stringify(obj);}}
這也適用於數字和字串字面量。
示例
tsconst Foo = "Foo";const Bar = "Bar";let x = {[Foo]: 100,[Bar]: "hello"};let a = x[Foo]; // has type 'number'let b = x[Bar]; // has type 'string'
unique symbol
為了能夠將符號視為唯一的字面量,引入了一種新的型別 unique symbol。unique symbol 是 symbol 的子型別,僅透過呼叫 Symbol() 或 Symbol.for(),或者透過顯式型別標註生成。這種新型別僅允許用於 const 宣告和 readonly static 屬性;若要引用特定的唯一符號,必須使用 typeof 運算子。對 unique symbol 的每一次引用都暗示了一個與給定宣告繫結的完全唯一的標識。
示例
ts// Worksdeclare const Foo: unique symbol;// Error! 'Bar' isn't a constant.let Bar: unique symbol = Symbol();// Works - refers to a unique symbol, but its identity is tied to 'Foo'.let Baz: typeof Foo = Foo;// Also works.class C {static readonly StaticSymbol: unique symbol = Symbol();}
因為每個 unique symbol 都有完全獨立的標識,所以沒有任何兩個 unique symbol 型別是可以相互賦值或比較的。
示例
tsconst Foo = Symbol();const Bar = Symbol();// Error: can't compare two unique symbols.if (Foo === Bar) {// ...}
嚴格類初始化
TypeScript 2.7 引入了一個名為 strictPropertyInitialization 的新標誌。該標誌執行檢查以確保類的每個例項屬性都在建構函式主體中或透過屬性初始化器進行初始化。例如
tsclass C {foo: number;bar = "hello";baz: boolean;// ~~~// Error! Property 'baz' has no initializer and is not definitely assigned in the// constructor.constructor() {this.foo = 42;}}
在上面程式碼中,如果我們確實打算讓 baz 可能為 undefined,我們應該將其宣告為 boolean | undefined 型別。
在某些情況下,屬性可以被間接初始化(例如透過輔助方法或依賴注入庫),在這種情況下,你可以為你的屬性使用新的確定賦值斷言修飾符(見下文討論)。
tsclass C {foo!: number;// ^// Notice this '!' modifier.// This is the "definite assignment assertion"constructor() {this.initialize();}initialize() {this.foo = 0;}}
請記住,strictPropertyInitialization 將與其他 strict 模式標誌一起開啟,這可能會影響你的專案。你可以在 tsconfig.json 的 compilerOptions 中將 strictPropertyInitialization 設定為 false,或者在命令列中使用 --strictPropertyInitialization false 來關閉此項檢查。
確定賦值斷言
確定賦值斷言是一項允許在例項屬性和變數聲明後放置 ! 的功能,用於向 TypeScript 傳達變數在所有意圖和目的下確實已被賦值,即使 TypeScript 的分析無法檢測到這一點。
示例
tslet x: number;initialize();console.log(x + x);// ~ ~// Error! Variable 'x' is used before being assigned.function initialize() {x = 10;}
透過確定賦值斷言,我們可以在聲明後附加一個 ! 來斷言 x 確實已被賦值。
ts// Notice the '!'let x!: number;initialize();// No error!console.log(x + x);function initialize() {x = 10;}
從某種意義上說,確定賦值斷言運算子是非空斷言運算子的對偶(其中表示式字尾為 !),我們也可以在示例中使用它。
tslet x: number;initialize();// No error!console.log(x! + x!);function initialize() {x = 10;}
在我們的示例中,我們知道 x 的所有用法都將被初始化,因此使用確定賦值斷言比非空斷言更有意義。
固定長度元組
在 TypeScript 2.6 及更早版本中,[number, string, string] 被認為是 [number, string] 的子型別。這是由 TypeScript 的結構化特性所驅動的;[number, string, string] 的第一個和第二個元素分別是 [number, string] 的第一個和第二個元素的子型別。然而,在檢查了現實世界中元組的使用情況後,我們注意到大多數允許這種情況發生的情況通常是不可取的。
在 TypeScript 2.7 中,不同元數(arity)的元組不再可以相互賦值。得益於 Kiara Grouwstra 的 pull request,元組型別現在將其元數編碼到它們各自 length 屬性的型別中。這是透過利用數字字面量型別實現的,現在允許元組與不同元數的元組區分開來。
從概念上講,你可以認為型別 [number, string] 等同於以下 NumStrTuple 的宣告
tsinterface NumStrTuple extends Array<number | string> {0: number;1: string;length: 2; // using the numeric literal type '2'}
注意,這對某些程式碼來說是一個破壞性變更。如果你需要恢復到元組僅強制要求最小長度的原始行為,你可以使用一個類似的宣告,但不顯式定義 length 屬性,從而回退到 number。
tsinterface MinimumNumStrTuple extends Array<number | string> {0: number;1: string;}
請注意,這並不意味著元組代表不可變陣列,但這是一種隱含的約定。
改進物件字面量的型別推斷
TypeScript 2.7 改進了對同一上下文中出現的多個物件字面量的型別推斷。當多個物件字面量型別對聯合型別做出貢獻時,我們現在規範化物件字面量型別,使得所有屬性都存在於聯合型別的每個組成部分中。
考慮
tsconst obj = test ? { text: "hello" } : {}; // { text: string } | { text?: undefined }const s = obj.text; // string | undefined
以前,obj 被推斷為 {} 型別,第二行隨後導致錯誤,因為 obj 看起來沒有任何屬性。這顯然不理想。
示例
ts// let obj: { a: number, b: number } |// { a: string, b?: undefined } |// { a?: undefined, b?: undefined }let obj = [{ a: 1, b: 2 }, { a: "abc" }, {}][0];obj.a; // string | number | undefinedobj.b; // number | undefined
針對同一型別引數的多個物件字面量型別推斷類似地被合併為一個單一的規範化聯合型別。
tsdeclare function f<T>(...items: T[]): T;// let obj: { a: number, b: number } |// { a: string, b?: undefined } |// { a?: undefined, b?: undefined }let obj = f({ a: 1, b: 2 }, { a: "abc" }, {});obj.a; // string | number | undefinedobj.b; // number | undefined
改進對結構相同類和 instanceof 表示式的處理
TypeScript 2.7 改進了對聯合型別中結構相同類和 instanceof 表示式的處理。
- 結構相同但不同的類型別現在會在聯合型別中得到保留(而不是消除除一個之外的所有型別)。
- 聯合型別子型別縮減僅在類型別是聯合型別中另一個類型別的子類且派生自該類時,才會將其移除。
instanceof運算子的型別檢查現在基於左運算元的型別是否派生自右運算元指示的型別(而不是結構子型別檢查)。
這意味著聯合型別和 instanceof 可以正確區分結構相同的類。
示例
tsclass A {}class B extends A {}class C extends A {}class D extends A {c: string;}class E extends D {}let x1 = !true ? new A() : new B(); // Alet x2 = !true ? new B() : new C(); // B | C (previously B)let x3 = !true ? new C() : new D(); // C | D (previously C)let a1 = [new A(), new B(), new C(), new D(), new E()]; // A[]let a2 = [new B(), new C(), new D(), new E()]; // (B | C | D)[] (previously B[])function f1(x: B | C | D) {if (x instanceof B) {x; // B (previously B | D)} else if (x instanceof C) {x; // C} else {x; // D (previously never)}}
從 in 運算子推斷出的型別守衛
in 運算子現在充當型別的收縮表示式。
對於 n in x 表示式,其中 n 是字串字面量或字串字面量型別,x 是聯合型別,“true”分支收縮為具有可選或必需屬性 n 的型別,“false”分支收縮為具有可選或缺失屬性 n 的型別。
示例
tsinterface A {a: number;}interface B {b: string;}function foo(x: A | B) {if ("a" in x) {return x.a;}return x.b;}
透過 --esModuleInterop 支援從 CommonJS 模組使用 import d from "cjs"
TypeScript 2.7 更新了 CommonJS/AMD/UMD 模組的編譯輸出,以在 esModuleInterop 下根據 __esModule 指示符的存在來合成名稱空間記錄。這一變化使 TypeScript 生成的輸出更接近 Babel 生成的輸出。
以前,CommonJS/AMD/UMD 模組的處理方式與 ES6 模組相同,導致了一些問題。具體來說:
- 對於 CommonJS/AMD/UMD 模組,TypeScript 將名稱空間匯入(即
import * as foo from "foo")視為等同於const foo = require("foo")。這在處理時很簡單,但如果被匯入的主要物件是原始型別、類或函式,則無法正常工作。ECMAScript 規範規定名稱空間記錄是一個普通物件,且名稱空間匯入(上例中的foo)不可呼叫,儘管 TypeScript 允許這樣做。 - 同樣,對於 CommonJS/AMD/UMD 模組的預設匯入(即
import d from "foo"),相當於const d = require("foo").default。當今大多數可用的 CommonJS/AMD/UMD 模組都沒有default匯出,這使得此匯入模式在匯入非 ES 模組(如 CommonJS/AMD/UMD)時幾乎不可用。例如import fs from "fs"或import express from "express"是不允許的。
在新的 esModuleInterop 下,這兩個問題都應得到解決:
- 名稱空間匯入(即
import * as foo from "foo")現在被正確標記為不可呼叫。呼叫它將導致錯誤。 - 現在允許對 CommonJS/AMD/UMD 進行預設匯入(例如
import fs from "fs"),並且應該按預期工作。
注意:新行為新增在標誌位下,以避免對現有程式碼庫產生不必要的破壞。我們強烈建議將其應用於新專案和現有專案。對於現有專案,名稱空間匯入(
import * as express from "express"; express();)需要轉換為預設匯入(import express from "express"; express();)。
示例
使用 esModuleInterop,會生成兩個新的輔助函式 __importStar 和 __importDefault,分別用於 import * 和 import default。例如輸入如下:
tsimport * as foo from "foo";import b from "bar";
將生成:
js"use strict";var __importStar =(this && this.__importStar) ||function(mod) {if (mod && mod.__esModule) return mod;var result = {};if (mod != null)for (var k in mod)if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];result["default"] = mod;return result;};var __importDefault =(this && this.__importDefault) ||function(mod) {return mod && mod.__esModule ? mod : { default: mod };};exports.__esModule = true;var foo = __importStar(require("foo"));var bar_1 = __importDefault(require("bar"));
數字分隔符
TypeScript 2.7 支援 ES 數字分隔符。數字字面量現在可以使用 _ 分隔為段。
示例
tsconst million = 1_000_000;const phone = 555_734_2231;const bytes = 0xff_0c_00_ff;const word = 0b1100_0011_1101_0001;
--watch 模式下更整潔的輸出
TypeScript 的 --watch 模式現在在請求重新編譯後會清除螢幕。
更漂亮的 --pretty 輸出
TypeScript 的 pretty 標誌可以使錯誤訊息更易於閱讀和管理。pretty 現在為檔名、診斷程式碼和行號使用顏色。檔名和位置現在也被格式化,以便在常用終端(例如 Visual Studio Code 終端)中進行導航。