类型推断 Type Inference-官网Reference

127 阅读3分钟

Type Inference

在TypeScript中,有几个地方在没有显式类型注释的情况下,会使用类型推断来提供类型信息。例子:

let x = 3;
// let x: number

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

在大多数情况下,类型推断是直接的。接下来我们将探讨类型推断方式中的一些细微差别。

最佳公共类型

当类型推断来自多个表达式时,将使用这些表达式的类型计算出 "最佳公共类型"。例子:

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

上面的例子中推断 x 的类型,必须考虑每个数组元素的类型。这里提供了数组元素类型两种选择, numbernull。最佳公共类型算法会考虑每个候选类型,并选择的类型与其它所有候选类型兼容。

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

class Animal { }
class Rhino extends Animal {
  hasHorn!: true;
}
class Elephant extends Animal {
  hasTrunk!: true;
}
class Snake extends Animal {
  hasLegs!: false;
}
// ---cut---
let zoo = [new Rhino(), new Elephant(), new Snake()];
// let zoo: (Rhino | Elephant | Snake)[]

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

理想情况下,我们可能希望将 zoo 推断为一个 Animal[] 类型,但是因为数组中没有严格类型为 Animal 的对象,所以推断成了一个联合数组类型。

我们可以不推断数组元素的类型,直接提供一个显式类型,例子:

class Animal { }
class Rhino extends Animal {
  hasHorn!: true;
}
class Elephant extends Animal {
  hasTrunk!: true;
}
class Snake extends Animal {
  hasLegs!: false;
}
// ---cut---
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];
// let zoo: Animal[]

上下文类型化

在 TypeScript 的某些情况下,类型推断也会以 "另一个方向" 工作。就是所谓的 "上下文类型"。当表达式通过其所处的位置而产生隐性类型,就是上下文类型化。例如:

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

例子中,TypeScript 类型检查器使用 Window.onmousedown 方法类型去推断在赋值右侧的函数表达式类型。它可以推断出参数 mouseEvent类型,并且包含 button 属性,但是没有 kangaroo 属性。

这是因为 window 已经在它的类型中声明了 onmousedown

// 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 足够聪明,可以在其它上下文中推断类型:

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

例子中,TypeScript 知道 uiEventUIEvent,而不是像之前例子的 MouseEventUIEvent 对象没有 button 属性,所以 TypeScript 将抛出异常。

如果这个函数没有在上下文类型位置,那么函数参数将具有隐性的 any 类型,并且不会发出错误(除非你启用 noImolicitAny 选项):

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

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

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

例子中,将打印 undefined,因为 uiEvent 没有 button 属性。

上下文类型化应用在许多场景,如:函数调用的参数、赋值右侧、类型断言、对象和数组字面量成员、返回语句。上下文类型也充当最佳公共类型中的候选类型。例子:

class Animal { }
class Rhino extends Animal {
  hasHorn!: true;
}
class Elephant extends Animal {
  hasTrunk!: true;
}
class Snake extends Animal {
  hasLegs!: false;
}
// ---cut---
function createZoo(): Animal[] {
    return [new Rhino(), new Elephant(), new Snake()];
}

例子中,最佳公共类型有四个候选类型:AnimalRhinoElephantSnake。其中 Animal 可以通过最佳公共类型算法来选择。

感谢观看,如有错误,望指正

官网文档地址: www.typescriptlang.org/docs/handbo…

本章已上传 github: github.com/Mario-Mario…