类型推断

在 TypeScript 中,有几个地方在没有显式类型注释的情况下使用类型推断来提供类型信息。例如,在这段代码中

¥In TypeScript, there are several places where type inference is used to provide type information when there is no explicit type annotation. For example, in this code

ts
let x = 3;
let x: number
Try

x 变量的类型被推断为 number。这种推断发生在初始化变量和成员、设置参数默认值以及确定函数返回类型时。

¥The type of the x variable is inferred to be number. This kind of inference takes place when initializing variables and members, setting parameter default values, and determining function return types.

在大多数情况下,类型推断很简单。在接下来的部分中,我们将探讨如何推断类型的一些细微差别。

¥In most cases, type inference is straightforward. In the following sections, we’ll explore some of the nuances in how types are inferred.

最佳常见类型

¥Best common type

当从多个表达式进行类型推断时,这些表达式的类型用于计算 “最常见的类型”。例如,

¥When a type inference is made from several expressions, the types of those expressions are used to calculate a “best common type”. For example,

ts
let x = [0, 1, null];
let x: (number | null)[]
Try

要推断上例中 x 的类型,我们必须考虑每个数组元素的类型。在这里,我们为数组类型提供了两种选择:numbernull。最佳通用类型算法会考虑每种候选类型,并选择与所有其他候选类型兼容的类型。

¥To infer the type of x in the example above, we must consider the type of each array element. Here we are given two choices for the type of the array: number and null. The best common type algorithm considers each candidate type, and picks the type that is compatible with all the other candidates.

因为必须从提供的候选类型中选择最佳公共类型,所以在某些情况下类型共享公共结构,但没有一种类型是所有候选类型的超类型。例如:

¥Because the best common type has to be chosen from the provided candidate types, there are some cases where types share a common structure, but no one type is the super type of all candidate types. For example:

ts
let zoo = [new Rhino(), new Elephant(), new Snake()];
let zoo: (Rhino | Elephant | Snake)[]
Try

理想情况下,我们可能希望将 zoo 推断为 Animal[],但由于数组中没有严格属于 Animal 类型的对象,因此我们不推断数组元素类型。要纠正此问题,请在没有一种类型是所有其他候选类型的超类型时显式提供类型:

¥Ideally, we may want zoo to be inferred as an Animal[], but because there is no object that is strictly of type Animal in the array, we make no inference about the array element type. To correct this, explicitly provide the type when no one type is a super type of all other candidates:

ts
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];
let zoo: Animal[]
Try

当没有找到最佳公共类型时,结果推断是联合数组类型 (Rhino | Elephant | Snake)[]

¥When no best common type is found, the resulting inference is the union array type, (Rhino | Elephant | Snake)[].

上下文类型

¥Contextual Typing

在某些情况下,类型推断在 “另一个方向” 中也适用于 TypeScript。这被称为 “上下文类型”。当表达式的类型由其位置隐含时,就会发生上下文类型。例如:

¥Type inference also works in “the other direction” in some cases in TypeScript. This is known as “contextual typing”. Contextual typing occurs when the type of an expression is implied by its location. For example:

ts
window.onmousedown = function (mouseEvent) {
console.log(mouseEvent.button);
console.log(mouseEvent.kangaroo);
Property 'kangaroo' does not exist on type 'MouseEvent'.2339Property 'kangaroo' does not exist on type 'MouseEvent'.
};
Try

在这里,TypeScript 类型检查器使用 Window.onmousedown 函数的类型来推断赋值右侧的函数表达式的类型。当它这样做时,它能够推断出 mouseEvent 参数的 type,它确实包含 button 属性,但不包含 kangaroo 属性。

¥Here, the TypeScript type checker used the type of the Window.onmousedown function to infer the type of the function expression on the right hand side of the assignment. When it did so, it was able to infer the type of the mouseEvent parameter, which does contain a button property, but not a kangaroo property.

这是有效的,因为 window 已经在其类型中声明了 onmousedown

¥This works because window already has onmousedown declared in its type:

ts
// Declares there is a global variable called 'window'
declare var window: Window & typeof globalThis;
// Which is declared as (simplified):
interface Window extends GlobalEventHandlers {
// ...
}
// Which defines a lot of known handler events
interface GlobalEventHandlers {
onmousedown: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null;
// ...
}

TypeScript 足够聪明,可以在其他上下文中推断类型:

¥TypeScript is smart enough to infer types in other contexts as well:

ts
window.onscroll = function (uiEvent) {
console.log(uiEvent.button);
Property 'button' does not exist on type 'Event'.2339Property 'button' does not exist on type 'Event'.
};
Try

基于上面的函数被分配给 Window.onscroll 的事实,TypeScript 知道 uiEvent 是一个 UIEvent,而不是像前面的例子那样的一个 MouseEventUIEvent 对象不包含 button 属性,因此 TypeScript 会抛出错误。

¥Based on the fact that the above function is being assigned to Window.onscroll, TypeScript knows that uiEvent is a UIEvent, and not a MouseEvent like the previous example. UIEvent objects contain no button property, and so TypeScript will throw an error.

如果此函数不在上下文类型位置,则函数的参数将隐式具有类型 any,并且不会触发错误(除非你使用 noImplicitAny 选项):

¥If this function were not in a contextually typed position, the function’s argument would implicitly have type any, and no error would be issued (unless you are using the noImplicitAny option):

ts
const handler = function (uiEvent) {
console.log(uiEvent.button); // <- OK
};
Try

我们还可以显式地为函数的参数提供类型信息以覆盖任何上下文类型:

¥We can also explicitly give type information to the function’s argument to override any contextual type:

ts
window.onscroll = function (uiEvent: any) {
console.log(uiEvent.button); // <- Now, no error is given
};
Try

但是,此代码将记录 undefined,因为 uiEvent 没有名为 button 的属性。

¥However, this code will log undefined, since uiEvent has no property called button.

上下文类型适用于许多情况。常见情况包括函数调用的参数、赋值的右侧、类型断言、对象和数组字面量的成员以及返回语句。上下文类型还充当最佳通用类型中的候选类型。例如:

¥Contextual typing applies in many cases. Common cases include arguments to function calls, right hand sides of assignments, type assertions, members of object and array literals, and return statements. The contextual type also acts as a candidate type in best common type. For example:

ts
function createZoo(): Animal[] {
return [new Rhino(), new Elephant(), new Snake()];
}
Try

在此示例中,最佳常见类型有一组四个候选者:AnimalRhinoElephantSnake。其中,Animal 可以通过最佳通用类型算法来选择。

¥In this example, best common type has a set of four candidates: Animal, Rhino, Elephant, and Snake. Of these, Animal can be chosen by the best common type algorithm.