原文地址:Type Inference - Reference | TypeScript Docs

在某些没有显式提供类型注释的地方,TypeScript 会通过叫做“类型推断”的方式提供类型信息,比如这个例子:

  1. let x = 3; // Type: number

变量 x 的类型被推断为 number,在初始化变量值、初始化属性值、设置函数参数默认值、决定函数返回值等场景中,这种推断方式就会介入。

大多数情况下,类型推断都比较简单直接,然而在个别情况下,也会有一些细微的区别。

最通用类型(Best common type)

当从多个表达式推断类型时,最终得到的类型是“最通用类型”,比如:

  1. let x = [0, 1, null]; // Type: (number | null)[]

上面这个例子中,为了推断 x 的类型,必需考虑到数组中每一个成员的类型,这里数组中包含 numbernull 两种类型,用于推断的成员类型也叫做候选类型,最终得到的类型需要兼容所有候选类型。

需要注意的是,最通用类型是严格从候选类型中选择的,所以,即使候选类型都拥有共同的父类,也无法做出父类的推断,比如:

  1. let zoo = [new Rhino(), new Elephant(), new Snake()]; // Type: (Rhino | Elephant | Snake)[]

这个例子中,我们可能希望 zoo 的类型被推断为 Animal[],但由于候选类型中没有任何一个成员的类型是 Animal,也就无法做出这样的推断,如果想得到 x: Animal[] 的推断,请显式声明。

  1. let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];

上下文推断(Contexual Typing)

TypeScript 中还有一种叫做“上下文推断”的类型推断方式,当某个未显式声明类型的目标的类型可以由上下文类型推断出来时,这种推断方式就会生效。

  1. window.onmousedown = function (mouseEvent) {
  2. console.log(mouseEvent.button);
  3. console.log(mouseEvent.kangaroo); // Error: Property 'kangaroo' does not exist on type 'MouseEvent'.
  4. };

这个例子中,TypeScript 会使用 window.onmousedown 的类型签名检验赋值符号右侧的函数。此时它能够知道右侧函数的 mouseEvent 参数类型是 MouseEvent,这个类型有 button 属性,但没有 kangaroo 属性,所以会报错。

当然,TypeScript 之所以这样做的前提是,window.onmousedown 的类型签名已经提供了。

  1. // Declares there is a global variable called 'window'
  2. declare var window: Window & typeof globalThis;
  3. // Which is declared as (simplified):
  4. interface Window extends GlobalEventHandlers {
  5. // ...
  6. }
  7. // Which defines a lot of known handler events
  8. interface GlobalEventHandlers {
  9. onmousedown: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null;
  10. // ...
  11. }

在其它情况下,这种推断方式同样有效:

window.onscroll = function (uiEvent) {
  console.log(uiEvent.button); // Error: Property 'button' does not exist on type 'Event'.
};

TypeScript 知道 uiEvent 的类型是 UIEvent,而不是 MouseEventUIEvent 没有 button 属性,所以报错。

但是,如果上述赋值符号右侧的函数无法建立有效的上下文推断信息,它的参数会被推断为 any,也就不会报错:

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

显式声明的类型优先级要高于类型推断,即使在有上下文信息的情况下,如果我们为函数参数显式声明了类型,TypeScript 会尊重我们的声明:

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

实际上 console.log(uiEvent.button) 会输出 undefined,因为 uiEvent 并没有 button 属性。

常见的上下文推断发生场景包括但不限于函数调用的参数、赋值、类型断言、对象和数组字面量、返回声明等。

上下文类型在最通用类型推断中也充当候选类型:

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

这个例子中,最通用类型的候选类型包括:AnimalRhinoElephantSnakeAnimal 当然是可以作为最通用类型的。