TypeScript 3.3

改进了调用联合的行为类型

🌐 Improved behavior for calling union types

在之前的 TypeScript 版本中,只有当可调用类型的联合具有相同的参数列表时,才能调用它们。

🌐 In prior versions of TypeScript, unions of callable types could only be invoked if they had identical parameter lists.

ts
type Fruit = "apple" | "orange";
type Color = "red" | "orange";
type FruitEater = (fruit: Fruit) => number; // eats and ranks the fruit
type ColorConsumer = (color: Color) => string; // consumes and describes the colors
declare let f: FruitEater | ColorConsumer;
// Cannot invoke an expression whose type lacks a call signature.
// Type 'FruitEater | ColorConsumer' has no compatible call signatures.ts(2349)
f("orange");

然而,在上述示例中,FruitEaterColorConsumer 都应该能够接受字符串 "orange",并返回 numberstring

🌐 However, in the above example, both FruitEaters and ColorConsumers should be able to take the string "orange", and return either a number or a string.

在 TypeScript 3.3 中,这不再是一个错误。

🌐 In TypeScript 3.3, this is no longer an error.

ts
type Fruit = "apple" | "orange";
type Color = "red" | "orange";
type FruitEater = (fruit: Fruit) => number; // eats and ranks the fruit
type ColorConsumer = (color: Color) => string; // consumes and describes the colors
declare let f: FruitEater | ColorConsumer;
f("orange"); // It works! Returns a 'number | string'.
f("apple"); // error - Argument of type '"apple"' is not assignable to parameter of type '"orange"'.
f("red"); // error - Argument of type '"red"' is not assignable to parameter of type '"orange"'.

在 TypeScript 3.3 中,这些签名的参数会被 交集 到一起,以创建一个新的签名。

🌐 In TypeScript 3.3, the parameters of these signatures are intersected together to create a new signature.

在上面的示例中,参数 fruitcolor 被交集成一个新的类型为 Fruit & Color 的参数。 Fruit & Color 实际上与 ("apple" | "orange") & ("red" | "orange") 相同,而 ("apple" | "orange") & ("red" | "orange") 等价于 ("apple" & "red") | ("apple" & "orange") | ("orange" & "red") | ("orange" & "orange")。 这些不可能的交集每一个都会简化为 never,然后我们剩下的是 "orange" & "orange",它只是 "orange"

🌐 In the example above, the parameters fruit and color are intersected together to a new parameter of type Fruit & Color. Fruit & Color is really the same as ("apple" | "orange") & ("red" | "orange") which is equivalent to ("apple" & "red") | ("apple" & "orange") | ("orange" & "red") | ("orange" & "orange"). Each of those impossible intersections reduces to never, and we’re left with "orange" & "orange" which is just "orange".

警告

🌐 Caveats

这种新行为仅在联合类型中最多只有一个类型有多个重载,并且联合类型中最多只有一个类型有泛型签名时才会生效。这意味着像 map(它是泛型)的 number[] | string[] 上的方法仍然无法调用。

🌐 This new behavior only kicks in when at most one type in the union has multiple overloads, and at most one type in the union has a generic signature. That means methods on number[] | string[] like map (which is generic) still won’t be callable.

另一方面,像 forEach 这样的方法现在可以被调用,但在 noImplicitAny 下可能会有一些问题。

🌐 On the other hand, methods like forEach will now be callable, but under noImplicitAny there may be some issues.

ts
interface Dog {
kind: "dog";
dogProp: any;
}
interface Cat {
kind: "cat";
catProp: any;
}
const catOrDogArray: Dog[] | Cat[] = [];
catOrDogArray.forEach(animal => {
// ~~~~~~ error!
// Parameter 'animal' implicitly has an 'any' type.
});

在 TypeScript 3.3 中,这仍然功能更强大,添加显式类型注释即可正常工作。

🌐 This is still strictly more capable in TypeScript 3.3, and adding an explicit type annotation will work.

ts
interface Dog {
kind: "dog";
dogProp: any;
}
interface Cat {
kind: "cat";
catProp: any;
}
const catOrDogArray: Dog[] | Cat[] = [];
catOrDogArray.forEach((animal: Dog | Cat) => {
if (animal.kind === "dog") {
animal.dogProp;
// ...
} else if (animal.kind === "cat") {
animal.catProp;
// ...
}
});

--build --watch 中复合项目的增量文件监控

🌐 Incremental file watching for composite projects in --build --watch

TypeScript 3.0 引入了一项用于构建结构的新功能,称为“复合项目”。 这里的部分目标是确保用户可以将大型项目拆分为构建快速且保持项目结构的小部分,而不会影响现有的 TypeScript 体验。 得益于复合项目,TypeScript 可以使用 --build 模式仅重新编译指定的项目及其依赖。 你可以将其理解为优化项目间构建。

🌐 TypeScript 3.0 introduced a new feature for structuring builds called “composite projects”. Part of the goal here was to ensure users could break up large projects into smaller parts that build quickly and preserve project structure, without compromising the existing TypeScript experience. Thanks to composite projects, TypeScript can use --build mode to recompile only the set of projects and dependencies. You can think of this as optimizing inter-project builds.

TypeScript 2.7 还通过新的增量“构建器”API 引入了 --watch 模式构建。类似地,其核心思想是该模式只会重新检查和重新输出已更改的文件或其依赖可能影响类型检查的文件。你可以将其视为对项目内部构建的一种优化。

🌐 TypeScript 2.7 also introduced --watch mode builds via a new incremental “builder” API. In a similar vein, the entire idea is that this mode only re-checks and re-emits changed files or files whose dependencies might impact type-checking. You can think of this as optimizing intra-project builds.

在 3.3 之前,使用 --build --watch 构建复合项目实际上并没有使用这种增量文件监视机制。在 --build --watch 模式下对一个项目进行更新会强制对该项目进行完整构建,而不是确定该项目中哪些文件受到影响。

🌐 Prior to 3.3, building composite projects using --build --watch actually didn’t use this incremental file watching infrastructure. An update in one project under --build --watch mode would force a full build of that project, rather than determining which files within that project were affected.

在 TypeScript 3.3 中,--build 模式的 --watch 标志确实也利用了增量文件监控。
这可能意味着在 --build --watch 下构建速度会显著加快。
在我们的测试中,这项功能使原本的 --build --watch 构建时间减少了 50% 到 75%
你可以查看这一更改的原始 pull request 以了解具体数据,但我们相信大多数复合项目的用户将在这里看到显著的收益。

🌐 In TypeScript 3.3, --build mode’s --watch flag does leverage incremental file watching as well. That can mean significantly faster builds under --build --watch. In our testing, this functionality has resulted in a reduction of 50% to 75% in build times of the original --build --watch times. You can read more on the original pull request for the change to see specific numbers, but we believe most composite project users will see significant wins here.