TypeScript 3.2

strictBindCallApply

TypeScript 3.2 引入了一个新的 strictBindCallApply 编译器选项(属于 strict 选项系列),使用该选项,函数对象上的 bindcallapply 方法将被强类型化并经过严格检查。

¥TypeScript 3.2 introduces a new strictBindCallApply compiler option (in the strict family of options) with which the bind, call, and apply methods on function objects are strongly typed and strictly checked.

ts
function foo(a: number, b: string): string {
return a + b;
}
let a = foo.apply(undefined, [10]); // error: too few arguments
let b = foo.apply(undefined, [10, 20]); // error: 2nd argument is a number
let c = foo.apply(undefined, [10, "hello", 30]); // error: too many arguments
let d = foo.apply(undefined, [10, "hello"]); // okay! returns a string

这是通过在 lib.d.ts 中引入两种新类型 CallableFunctionNewableFunction 来实现的。这些类型分别包含针对常规函数和构造函数的 bindcallapply 的专用泛型方法声明。声明使用泛型剩余参数(参见 #24897)以强类型方式捕获和反映参数列表。在 strictBindCallApply 模式下,这些声明用于代替类型 Function 提供的(非常宽松的)声明。

¥This is achieved by introducing two new types, CallableFunction and NewableFunction, in lib.d.ts. These types contain specialized generic method declarations for bind, call, and apply for regular functions and constructor functions, respectively. The declarations use generic rest parameters (see #24897) to capture and reflect parameter lists in a strongly typed manner. In strictBindCallApply mode these declarations are used in place of the (very permissive) declarations provided by type Function.

警告

¥Caveats

由于更严格的检查可能会发现以前未报告的错误,因此这是 strict 模式的一项重大更改。

¥Since the stricter checks may uncover previously unreported errors, this is a breaking change in strict mode.

此外,此新功能的 另一个警告 之处在于,由于某些限制,bindcallapply 尚无法完全建模泛型函数或具有重载的函数。在泛型函数上使用这些方法时,类型参数将被替换为空对象类型 ({});在具有重载的函数上使用时,只有最后一个重载会被建模。

¥Additionally, another caveat of this new functionality is that due to certain limitations, bind, call, and apply can’t yet fully model generic functions or functions that have overloads. When using these methods on a generic function, type parameters will be substituted with the empty object type ({}), and when used on a function with overloads, only the last overload will ever be modeled.

对象字面量中的泛型扩展表达式

¥Generic spread expressions in object literals

在 TypeScript 3.2 中,对象字面量现在允许泛型扩展表达式,这些表达式现在可以生成交叉类型,类似于 Object.assign 函数和 JSX 字面量。例如:

¥In TypeScript 3.2, object literals now allow generic spread expressions which now produce intersection types, similar to the Object.assign function and JSX literals. For example:

ts
function taggedObject<T, U extends string>(obj: T, tag: U) {
return { ...obj, tag }; // T & { tag: U }
}
let x = taggedObject({ x: 10, y: 20 }, "point"); // { x: number, y: number } & { tag: "point" }

属性赋值和非泛型扩展表达式将尽可能合并到泛型扩展表达式的两侧。例如:

¥Property assignments and non-generic spread expressions are merged to the greatest extent possible on either side of a generic spread expression. For example:

ts
function foo1<T>(t: T, obj1: { a: string }, obj2: { b: string }) {
return { ...obj1, x: 1, ...t, ...obj2, y: 2 }; // { a: string, x: number } & T & { b: string, y: number }
}

非泛型扩展表达式继续像以前一样处理:调用和构造函数签名会被剥离,只保留非方法属性,对于同名属性,将使用最右边属性的类型。这与交叉类型形成对比,交叉类型连接调用和构造签名,保留所有属性,并将同名属性的类型相交。因此,相同类型的展开在通过泛型实例化创建时可能会产生不同的结果:

¥Non-generic spread expressions continue to be processed as before: Call and construct signatures are stripped, only non-method properties are preserved, and for properties with the same name, the type of the rightmost property is used. This contrasts with intersection types which concatenate call and construct signatures, preserve all properties, and intersect the types of properties with the same name. Thus, spreads of the same types may produce different results when they are created through instantiation of generic types:

ts
function spread<T, U>(t: T, u: U) {
return { ...t, ...u }; // T & U
}
declare let x: { a: string; b: number };
declare let y: { b: string; c: boolean };
let s1 = { ...x, ...y }; // { a: string, b: string, c: boolean }
let s2 = spread(x, y); // { a: string, b: number } & { b: string, c: boolean }
let b1 = s1.b; // string
let b2 = s2.b; // number & string

泛型对象剩余变量和参数

¥Generic object rest variables and parameters

TypeScript 3.2 还允许从泛型变量解构 rest 绑定。​​这是通过使用 lib.d.ts 中预定义的 PickExclude 辅助类型,并使用相关的泛型类型以及解构模式中其他绑定的名称来实现的。

¥TypeScript 3.2 also allows destructuring a rest binding from a generic variable. This is achieved by using the predefined Pick and Exclude helper types from lib.d.ts, and using the generic type in question as well as the names of the other bindings in the destructuring pattern.

ts
function excludeTag<T extends { tag: string }>(obj: T) {
let { tag, ...rest } = obj;
return rest; // Pick<T, Exclude<keyof T, "tag">>
}
const taggedPoint = { x: 10, y: 20, tag: "point" };
const point = excludeTag(taggedPoint); // { x: number, y: number }

BigInt

BigInt 是 ECMAScript 即将发布的提案的一部分,该提案允许我们在理论上对任意大的整数进行建模。TypeScript 3.2 引入了对 BigInt 的类型检查,并支持在以 esnext 为目标时生成 BigInt 字面量。

¥BigInts are part of an upcoming proposal in ECMAScript that allow us to model theoretically arbitrarily large integers. TypeScript 3.2 brings type-checking for BigInts, as well as support for emitting BigInt literals when targeting esnext.

TypeScript 中的 BigInt 支持引入了一种称为 bigint(全部小写)的新原始类型。你可以通过调用 BigInt() 函数或在任何整数数字字面量末尾添加 n 来写出 BigInt 字面量,从而获得 bigint

¥BigInt support in TypeScript introduces a new primitive type called the bigint (all lowercase). You can get a bigint by calling the BigInt() function or by writing out a BigInt literal by adding an n to the end of any integer numeric literal:

ts
let foo: bigint = BigInt(100); // the BigInt function
let bar: bigint = 100n; // a BigInt literal
// *Slaps roof of fibonacci function*
// This bad boy returns ints that can get *so* big!
function fibonacci(n: bigint) {
let result = 1n;
for (let last = 0n, i = 0n; i < n; i++) {
const current = result;
result += last;
last = current;
}
return result;
}
fibonacci(10000n);

虽然你可能想象 numberbigint 之间会有密切的交互,但两者是不同的字段。

¥While you might imagine close interaction between number and bigint, the two are separate domains.

ts
declare let foo: number;
declare let bar: bigint;
foo = bar; // error: Type 'bigint' is not assignable to type 'number'.
bar = foo; // error: Type 'number' is not assignable to type 'bigint'.

根据 ECMAScript 中的规定,在算术运算中混合使用 numberbigint 是错误的。你必须将值显式转换为 BigInt

¥As specified in ECMAScript, mixing numbers and bigints in arithmetic operations is an error. You’ll have to explicitly convert values to BigInts.

ts
console.log(3.141592 * 10000n); // error
console.log(3145 * 10n); // error
console.log(BigInt(3145) * 10n); // okay!

同样需要注意的是,使用 typeof 运算符时,bigint 会生成一个新字符串:字符串 "bigint"。因此,TypeScript 可以像你预期的那样正确地使用 typeof 进行缩窄。

¥Also important to note is that bigints produce a new string when using the typeof operator: the string "bigint". Thus, TypeScript correctly narrows using typeof as you’d expect.

ts
function whatKindOfNumberIsIt(x: number | bigint) {
if (typeof x === "bigint") {
console.log("'x' is a bigint!");
} else {
console.log("'x' is a floating-point number");
}
}

我们要衷心感谢 Caleb Sander 为此功能所做的所有工作。我们确信我们的用户也是如此!

¥We’d like to extend a huge thanks to Caleb Sander for all the work on this feature. We’re grateful for the contribution, and we’re sure our users are too!

警告

¥Caveats

正如我们提到的,BigInt 支持仅适用于 esnext 目标。这可能并不明显,但由于 BigInt 对数学运算符(例如 +-* 等)的行为不同,因此为不存在该功能的旧目标平台(例如 es2017 及以下版本)提供功能将涉及重写每个操作。TypeScript 需要根据类型分派到正确的行为,因此每个加法、字符串连接、乘法等操作都将涉及函数调用。

¥As we mentioned, BigInt support is only available for the esnext target. It may not be obvious, but because BigInts have different behavior for mathematical operators like +, -, *, etc., providing functionality for older targets where the feature doesn’t exist (like es2017 and below) would involve rewriting each of these operations. TypeScript would need to dispatch to the correct behavior depending on the type, and so every addition, string concatenation, multiplication, etc. would involve a function call.

因此,我们目前没有提供降级支持的计划。好的一面是,Node 11 和较新版本的 Chrome 已经支持此功能,因此你可以在使用 esnext 时在那里使用 BigInt。

¥For that reason, we have no immediate plans to provide downleveling support. On the bright side, Node 11 and newer versions of Chrome already support this feature, so you’ll be able to use BigInts there when targeting esnext.

某些目标可能包含 polyfill 或类似 BigInt 的运行时对象。为此,你可能需要在编译器选项的 lib 设置中添加 esnext.bigint

¥Certain targets may include a polyfill or BigInt-like runtime object. For those purposes you may want to add esnext.bigint to the lib setting in your compiler options.

非单位类型作为联合判别式

¥Non-unit types as union discriminants

TypeScript 3.2 通过放宽判别属性的规则,使类型缩小变得更容易。只要联合体的通用属性包含某种单例类型(例如字符串字面量、nullundefined),并且不包含泛型,它们现在就被视为判别式。

¥TypeScript 3.2 makes narrowing easier by relaxing rules for what it considers a discriminant property. Common properties of unions are now considered discriminants as long as they contain some singleton type (e.g. a string literal, null, or undefined), and they contain no generics.

因此,TypeScript 3.2 将以下示例中的 error 属性视为判别式,而在此之前则不然,因为 Error 不是单例类型。得益于此,unwrap 函数体中的窄化功能得以正确运行。

¥As a result, TypeScript 3.2 considers the error property in the following example to be a discriminant, whereas before it wouldn’t since Error isn’t a singleton type. Thanks to this, narrowing works correctly in the body of the unwrap function.

ts
type Result<T> = { error: Error; data: null } | { error: null; data: T };
function unwrap<T>(result: Result<T>) {
if (result.error) {
// Here 'error' is non-null
throw result.error;
}
// Now 'data' is non-null
return result.data;
}

通过 Node.js 包继承 tsconfig.json

¥tsconfig.json inheritance via Node.js packages

TypeScript 3.2 现在可以从 node_modules 解析 tsconfig.json。在 tsconfig.json 中为 extends 字段使用裸路径时,TypeScript 将为我们深入研究 node_modules 包。

¥TypeScript 3.2 now resolves tsconfig.jsons from node_modules. When using a bare path for the extends field in tsconfig.json, TypeScript will dive into node_modules packages for us.

{
"": "@my-team/tsconfig-base",
"": ["./**/*"],
// Override certain options on a project-by-project basis.
}
}

这里,TypeScript 会爬遍 node_modules 文件夹,查找 @my-team/tsconfig-base 包。对于每个包,TypeScript 将首先检查 package.json 是否包含 "tsconfig" 字段,如果包含,TypeScript 将尝试从该字段加载配置文件。如果两者都不存在,TypeScript 将尝试从根目录的 tsconfig.json 读取。这类似于 Node 使用的包中 .js 文件的查找过程,以及 TypeScript 已在使用的 .d.ts 查找过程。

¥Here, TypeScript will climb up node_modules folders looking for a @my-team/tsconfig-base package. For each of those packages, TypeScript will first check whether package.json contains a "tsconfig" field, and if it does, TypeScript will try to load a configuration file from that field. If neither exists, TypeScript will try to read from a tsconfig.json at the root. This is similar to the lookup process for .js files in packages that Node uses, and the .d.ts lookup process that TypeScript already uses.

此功能对于大型组织或具有大量分布式依赖的项目非常有用。

¥This feature can be extremely useful for bigger organizations, or projects with lots of distributed dependencies.

新的 --showConfig 标志

¥The new --showConfig flag

tsc,TypeScript 编译器,支持一个名为 --showConfig 的新标志。运行 tsc --showConfig 时,TypeScript 会计算有效的 tsconfig.json(在计算从 extends 字段继承的选项之后)并将其打印出来。这对于诊断一般配置问题非常有用。

¥tsc, the TypeScript compiler, supports a new flag called --showConfig. When running tsc --showConfig, TypeScript will calculate the effective tsconfig.json (after calculating options inherited from the extends field) and print that out. This can be useful for diagnosing configuration issues in general.

JavaScript 中的 Object.defineProperty 声明

¥Object.defineProperty declarations in JavaScript

在 JavaScript 文件中(使用 allowJs)编写时,TypeScript 现在可以识别使用 Object.defineProperty 的声明。这意味着在 JavaScript 文件中启用类型检查(通过启用 checkJs 选项或在文件顶部添加 // @ts-check 注释)时,你将获得更好的补全和更强大的类型检查。

¥When writing in JavaScript files (using allowJs), TypeScript now recognizes declarations that use Object.defineProperty. This means you’ll get better completions, and stronger type-checking when enabling type-checking in JavaScript files (by turning on the checkJs option or adding a // @ts-check comment to the top of your file).

js
// @ts-check
let obj = {};
Object.defineProperty(obj, "x", { value: "hello", writable: false });
obj.x.toLowercase();
// ~~~~~~~~~~~
// error:
// Property 'toLowercase' does not exist on type 'string'.
// Did you mean 'toLowerCase'?
obj.x = "world";
// ~
// error:
// Cannot assign to 'x' because it is a read-only property.