常量命名属性
¥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
// 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);}}
这也适用于数字和字符串字面量。
¥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 symbol
可用。unique symbol
是 symbol
的子类型,仅通过调用 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
// 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
类型可以分配或相互比较。
¥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.json
的 compilerOptions
中将 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
声明后附加 !
来断言 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 的拉取请求,元组类型现在将其元数编码到其各自 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 | undefinedobj.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 | undefinedobj.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:
-
结构相同但不同的类类型现在保留在联合类型中(而不是只保留一个)。
¥Structurally identical, but distinct, class types are now preserved in union types (instead of eliminating all but one).
-
联合类型子类型缩减仅当某个类类型是联合中另一个类类型的子类且派生自该类类型时才会删除该类类型。
¥Union type subtype reduction only removes a class type if it is a subclass of and derives from another class type in the union.
-
instanceof
运算符的类型检查现在基于左操作数的类型是否源自右操作数指示的类型(而不是结构子类型检查)。¥Type checking of the
instanceof
operator is now based on whether the type of the left operand derives from the type indicated by the right operand (as opposed to a structural subtype check).
这意味着联合类型和 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(); // 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
运算符推断出的类型保护
¥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;}
支持从 CommonJS 模块和 --esModuleInterop
中调用 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 允许调用。¥TypeScript treats a namespace import (i.e.
import * as foo from "foo"
) for a CommonJS/AMD/UMD module as equivalent toconst foo = require("foo")
.Things are simple here, but they don’t work out if the primary object being imported is a primitive or a class or a function. ECMAScript spec stipulates that a namespace record is a plain object, and that a namespace import (foo
in the example above) is not callable, though allowed by 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"
。¥Similarly a default import (i.e.
import d from "foo"
) for a CommonJS/AMD/UMD module as equivalent toconst d = require("foo").default
.Most of the CommonJS/AMD/UMD modules available today do not have adefault
export, making this import pattern practically unusable to import non-ES modules (i.e. CommonJS/AMD/UMD). For instanceimport fs from "fs"
orimport express from "express"
are not allowed.
在新的 esModuleInterop
模式下,应该解决以下两个问题:
¥Under the new esModuleInterop
these two issues should be addressed:
-
命名空间导入(即
import * as foo from "foo"
)现在已正确标记为不可调用。调用它将导致错误。¥A namespace import (i.e.
import * as foo from "foo"
) is now correctly flagged as uncallable. Calling it will result in an error. -
现在允许默认导入 CommonJS/AMD/UMD(例如
import fs from "fs"
),并且应该可以正常工作。¥Default imports to CommonJS/AMD/UMD are now allowed (e.g.
import fs from "fs"
), and should work as expected.
注意:新行为通过标志添加,以避免对现有代码库造成不必要的破坏。我们强烈建议将其应用于新项目和现有项目。对于现有项目,命名空间导入 (
import * as express from "express"; express();
) 需要转换为默认导入 (import express from "express"; express();
)。¥Note: The new behavior is added under a flag to avoid unwarranted breaks to existing code bases. We highly recommend applying it both to new and existing projects. For existing projects, namespace imports (
import * as express from "express"; express();
) will need to be converted to default imports (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).