TypeScript 2.7

常量命名属性

🌐 Constant-named properties

TypeScript 2.7 增加了对在类型(包括 ECMAScript 符号)上声明 const 命名属性的支持。

🌐 TypeScript 2.7 adds support for declaring const-named properties on types including ECMAScript symbols.

示例

🌐 Example

ts
// Lib
export const SERIALIZE = Symbol("serialize-method-key");
export interface Serializable {
[SERIALIZE](obj: {}): string;
}
ts
// consumer
import { SERIALIZE, Serializable } from "lib";
class JSONSerializableItem implements Serializable {
[SERIALIZE](obj: {}) {
return JSON.stringify(obj);
}
}

这也适用于数字和字符串字面量。

🌐 This also applies to numeric and string literals.

示例

🌐 Example

ts
const 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 symbolunique symbolsymbol 的子类型,并且仅通过调用 Symbol()Symbol.for(),或通过显式类型注解生成。 这种新类型仅允许用于 const 声明和 readonly static 属性,并且为了引用特定的唯一符号,你必须使用 typeof 操作符。 对每个 unique symbol 的引用都意味着一个与给定声明绑定的完全唯一的标识。

🌐 To enable treating symbols as unique literals a new type unique symbol is available. unique symbol is a subtype of symbol, and are produced only from calling Symbol() or Symbol.for(), or from explicit type annotations. The new type is only allowed on const declarations and readonly static properties, and in order to reference a specific unique symbol, you’ll have to use the typeof operator. Each reference to a unique symbol implies a completely unique identity that’s tied to a given declaration.

示例

🌐 Example

ts
// Works
declare 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 类型都不能相互赋值或比较。

🌐 Because each unique symbol has a completely separate identity, no two unique symbol types are assignable or comparable to each other.

示例

🌐 Example

ts
const Foo = Symbol();
const Bar = Symbol();
// Error: can't compare two unique symbols.
if (Foo === Bar) {
// ...
}

严格的类初始化

🌐 Strict Class Initialization

TypeScript 2.7 引入了一个名为 strictPropertyInitialization 的新标志。这个标志会进行检查,以确保类的每个实例属性都在构造函数体内或通过属性初始化器进行初始化。例如

🌐 TypeScript 2.7 introduces a new flag called strictPropertyInitialization. This flag performs checks to ensure that each instance property of a class gets initialized in the constructor body, or by a property initializer. For example

ts
class 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 类型。

🌐 In the above, if we truly meant for baz to potentially be undefined, we should have declared it with the type boolean | undefined.

在某些情况下,属性可以通过间接方式初始化(例如通过辅助方法或依赖注入库),在这种情况下,你可以对属性使用新的 确定赋值断言修饰符(如下文所述)。

🌐 There are certain scenarios where properties can be initialized indirectly (perhaps by a helper method or dependency injection library), in which case you can use the new definite assignment assertion modifiers for your properties (discussed below).

ts
class C {
foo!: number;
// ^
// Notice this '!' modifier.
// This is the "definite assignment assertion"
constructor() {
this.initialize();
}
initialize() {
this.foo = 0;
}
}

请记住,strictPropertyInitialization 将与其他 strict 模式标志一起启用,这可能会影响你的项目。你可以在 tsconfig.jsoncompilerOptions 中将 strictPropertyInitialization 设置为 false,或者在命令行上使用 --strictPropertyInitialization false 来关闭此检查。

🌐 Keep in mind that strictPropertyInitialization will be turned on along with other strict mode flags, which can impact your project. You can set the strictPropertyInitialization setting to false in your tsconfig.json’s compilerOptions, or --strictPropertyInitialization false on the command line to turn off this checking.

明确赋值断言

🌐 Definite Assignment Assertions

确定赋值断言是一项功能,它允许在实例属性和变量声明之后使用 !,以向 TypeScript 传达变量在所有实际情况下确实已经被赋值,即使 TypeScript 的分析无法检测到这一点。

🌐 The definite assignment assertion is a feature that allows a ! to be placed after instance property and variable declarations to relay to TypeScript that a variable is indeed assigned for all intents and purposes, even if TypeScript’s analyses cannot detect so.

示例

🌐 Example

ts
let x: number;
initialize();
console.log(x + x);
// ~ ~
// Error! Variable 'x' is used before being assigned.
function initialize() {
x = 10;
}

使用确定赋值断言,我们可以通过在声明中添加 ! 来断言 x 确实已被赋值:

🌐 With definite assignment assertions, we can assert that x is really assigned by appending an ! to its declaration:

ts
// Notice the '!'
let x!: number;
initialize();
// No error!
console.log(x + x);
function initialize() {
x = 10;
}

在某种意义上,确定赋值断言操作符是非空断言操作符的对偶(其中 表达式 会后缀 !),我们在示例中也可以使用它。

🌐 In a sense, the definite assignment assertion operator is the dual of the non-null assertion operator (in which expressions are post-fixed with a !), which we could also have used in the example.

ts
let x: number;
initialize();
// No error!
console.log(x! + x!);
function initialize() {
x = 10;
}

在我们的例子中,我们知道 x 的所有使用都会被初始化,因此使用确定赋值断言比非空断言更合理。

🌐 In our example, we knew that all uses of x would be initialized so it makes more sense to use definite assignment assertions than non-null assertions.

固定长度元组

🌐 Fixed Length Tuples

在 TypeScript 2.6 及更早版本中,[number, string, string] 被认为是 [number, string] 的子类型。这是由于 TypeScript 的结构性特性驱动的;[number, string, string] 的第一个和第二个元素分别是 [number, string] 第一个和第二个元素的子类型。然而,在检查了元组的实际使用情况后,我们注意到大多数允许这种情况的情形通常都是不理想的。

🌐 In TypeScript 2.6 and earlier, [number, string, string] was considered a subtype of [number, string]. This was motivated by TypeScript’s structural nature; the first and second elements of a [number, string, string] are respectively subtypes of the first and second elements of [number, string]. However, after examining real world usage of tuples, we noticed that most situations in which this was permitted was typically undesirable.

在 TypeScript 2.7 中,不同元素数量的元组不再可以互相赋值。感谢 Kiara Grouwstra 的 pull request,现在元组类型会将它们的元素数量编码到各自的 length 属性的类型中。这是通过利用数字字面量类型来实现的,现在数字字面量类型允许元组与不同元素数量的元组区分开来。

🌐 In TypeScript 2.7, tuples of different arities are no longer assignable to each other. Thanks to a pull request from Kiara Grouwstra, tuple types now encode their arity into the type of their respective length property. This is accomplished by leveraging numeric literal types, which now allow tuples to be distinct from tuples of different arities.

从概念上讲,你可以认为类型 [number, string] 相当于以下 NumStrTuple 的声明:

🌐 Conceptually, you might consider the type [number, string] to be equivalent to the following declaration of NumStrTuple:

ts
interface NumStrTuple extends Array<number | string> {
0: number;
1: string;
length: 2; // using the numeric literal type '2'
}

请注意,这对某些代码来说是一个重大更改。如果你需要恢复到原来的行为,即元组仅强制执行最小长度,可以使用类似的声明,不显式定义 length 属性,而是回退到 number

🌐 Note that this is a breaking change for some code. If you need to resort to the original behavior in which tuples only enforce a minimum length, you can use a similar declaration that does not explicitly define a length property, falling back to number.

ts
interface MinimumNumStrTuple extends Array<number | string> {
0: number;
1: string;
}

请注意,这并不意味着元组表示不可变数组,而是一种隐含的约定。

🌐 Note that this does not imply tuples represent immutable arrays, but it is an implied convention.

改进了对象字面量的类型推断

🌐 Improved type inference for object literals

TypeScript 2.7 改进了在相同上下文中出现的多个对象字面量的类型推断。当多个对象字面量类型构成联合类型时,我们现在会对对象字面量类型进行 归一化,以确保联合类型的每个成员都拥有所有属性。

🌐 TypeScript 2.7 improves type inference for multiple object literals occurring in the same context. When multiple object literal types contribute to a union type, we now normalize the object literal types such that all properties are present in each constituent of the union type.

考虑:

🌐 Consider:

ts
const obj = test ? { text: "hello" } : {}; // { text: string } | { text?: undefined }
const s = obj.text; // string | undefined

之前 {} 被推断用于 obj,随后第二行导致了错误,因为 obj 似乎没有任何属性。这显然并不理想。

🌐 Previously type {} was inferred for obj and the second line subsequently caused an error because obj would appear to have no properties. That obviously wasn’t ideal.

示例

🌐 Example

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 | undefined
obj.b; // number | undefined

针对同一类型参数的多个对象字面量类型推断同样会被折叠成一个规范化的联合类型:

🌐 Multiple object literal type inferences for the same type parameter are similarly collapsed into a single normalized union type:

ts
declare 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 | undefined
obj.b; // number | undefined

改进了对结构相同类和 instanceof 表达式的处理

🌐 Improved handling of structurally identical classes and instanceof expressions

TypeScript 2.7 改进了联合类型和 instanceof 表达式中结构上相同类的处理:

🌐 TypeScript 2.7 improves the handling of structurally identical classes in union types and instanceof expressions:

  • 结构相同但不同的类类型现在保留在联合类型中(而不是只保留一个)。
  • 联合类型子类型缩减仅在某个类类型是另一个类类型的子类并且从联合中的另一个类类型继承时才会移除该类类型。
  • instanceof 操作符的类型检查现在基于左操作数的类型是否_派生自_右操作数所指示的类型(而不是结构子类型检查)。

这意味着联合类型和 instanceof 能够正确区分结构上相同的类。

🌐 This means that union types and instanceof properly distinguish between structurally identical classes.

示例

🌐 Example

ts
class 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(); // A
let 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 操作符推断的类型保护

🌐 Type guards inferred from in operator

in 运算符现在作为类型的缩小表达式。

🌐 The in operator now acts as a narrowing expression for types.

对于一个 n in x 表达式,其中 n 是字符串字面量或字符串字面量类型,x 是联合类型,“true” 分支会收缩为具有可选或必需属性 n 的类型,而 “false” 分支会收缩为具有可选或缺失属性 n 的类型。

🌐 For a n in x expression, where n is a string literal or string literal type and x is a union type, the “true” branch narrows to types which have an optional or required property n, and the “false” branch narrows to types which have an optional or missing property n.

示例

🌐 Example

ts
interface 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" 支持

🌐 Support for import d from "cjs" from CommonJS modules with --esModuleInterop

TypeScript 2.7 更新了 CommonJS/AMD/UMD 模块的输出方式,以基于 esModuleInterop__esModule 指示符的存在来合成命名空间记录。此更改使 TypeScript 生成的输出更接近 Babel 生成的输出。

🌐 TypeScript 2.7 updates CommonJS/AMD/UMD module emit to synthesize namespace records based on the presence of an __esModule indicator under esModuleInterop. The change brings the generated output from TypeScript closer to that generated by Babel.

以前,CommonJS/AMD/UMD 模块被视为与 ES6 模块相同,这导致了一些问题。具体如下:

🌐 Previously CommonJS/AMD/UMD modules were treated in the same way as ES6 modules, resulting in a couple of problems. Namely:

  • TypeScript 将 CommonJS/AMD/UMD 模块的命名空间导入(即 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,这两个问题应该得到解决:

🌐 Under the new esModuleInterop these two issues should be addressed:

  • 命名空间导入(即 import * as foo from "foo")现在被正确标记为不可调用。调用它将导致错误。
  • 现在允许默认导入到 CommonJS/AMD/UMD(例如 import fs from "fs"),并且应按预期工作。

注意:为了避免对现有代码库造成不必要的中断,新行为是通过标志添加的。我们强烈建议将其同时应用于新项目和现有项目。 对于现有项目,命名空间导入(import * as express from "express"; express();)需要转换为默认导入(import express from "express"; express();)。

示例

🌐 Example

使用 esModuleInterop 会生成两个新的辅助工具 __importStar__importDefault,分别用于导入 * 和 导入 default。例如,输入如下:

🌐 With esModuleInterop two new helpers are generated __importStar and __importDefault for import * and import default respectively. For instance input like:

ts
import * as foo from "foo";
import b from "bar";

将生成:

🌐 Will generate:

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"));

数字分隔符

🌐 Numeric separators

TypeScript 2.7 引入了对 ES 数字分隔符 的支持。数字字面量现在可以使用 _ 分隔为多个部分。

🌐 TypeScript 2.7 brings support for ES Numeric Separators. Numeric literals can now be separated into segments using _.

示例

🌐 Example

ts
const million = 1_000_000;
const phone = 555_734_2231;
const bytes = 0xff_0c_00_ff;
const word = 0b1100_0011_1101_0001;

--watch 模式下输出更清晰

🌐 Cleaner output in --watch mode

TypeScript 的 --watch 模式现在在请求重新编译后会清除屏幕。

🌐 TypeScript’s --watch mode now clears the screen after a re-compilation is requested.

更漂亮的 --pretty 输出

🌐 Prettier --pretty output

TypeScript 的 pretty 标志可以使错误信息更易于阅读和管理。 pretty 现在对文件名、诊断代码和行号使用颜色。 文件名和位置现在也被格式化,以便在常用终端中(例如 Visual Studio Code 终端)进行导航。

🌐 TypeScript’s pretty flag can make error messages easier to read and manage. pretty now uses colors for file names, diagnostic codes, and line numbers. File names and positions are now also formatted to allow navigation in common terminals (e.g. Visual Studio Code terminal).