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 还允许从泛型变量中解构剩余绑定。这可以通过使用 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() 函数获得一个 bigint,或者通过在任何整数数字字面量末尾添加 n 来写出 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!

同样需要注意的是,bigint 在使用 typeof 运算符时会生成一个新字符串:即字符串 "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 时,你能够在这些环境中使用 BigInts。

🌐 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;
}

tsconfig.json 通过 Node.js 包继承

🌐 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 字段继承的选项之后)并打印出来。
这对于一般诊断配置问题非常有用。

Object.defineProperty 声明在 JavaScript 中

🌐 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.