TypeScript Office - 类型推断
# 类型推断
在 TypeScript 中,有几个地方在没有显式类型注释的情况下,使用类型推理来提供类型信息。例如,在这段代码中:
// let x: number
let x = 3;
2
x 变量的类型被推断为 number。这种推断发生在初始化变量和成员、设置参数默认值和确定函数返回类型时。
在大多数情况下,类型推断是直截了当的。在下面的章节中,我们将探讨类型推断的一些细微差别。
# 最佳公共类型
当从几个表达式中进行类型推断时,这些表达式的类型被用来计算一个「最佳公共类型」。比如说:
// let x: (number | null)[]
let x = [0, 1, null];
2
为了推断上面例子中 x 的类型,我们必须考虑每个数组元素的类型。这里我们得到了两个数组类型的选择:number 和 null。最佳公共类型算法考虑了每个候选类型,并选择了与所有其他候选类型兼容的类型。
因为最佳公共类型必须从所提供的候选类型中选择,所以在某些情况下,类型有共同的结构,但没有一个类型是所有候选类型的超级类型。比如说:
// let zoo: (Rhino | Elephant | Snake)[]
let zoo = [new Rhino(), new Elephant(), new Snake()];
2
理想情况下,我们可能希望 zoo 被推断为 Animal[]
,但是因为数组中没有严格意义上的 Animal 类型的对象,所以我们没有对数组元素类型进行推断。为了纠正这一点,当没有一个类型是所有其他候选类型的超级类型时,就明确地提供类型。
// let zoo: Animal[]t
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];
2
当没有找到最好的共同类型时,产生的推论是联合数组类型,Rhino | Elephant | Snake)[]
。
# 上下文类型
在 TypeScript 的某些情况下,类型推理也在「另一个方向」发挥作用。这被称为「上下文类型化」。当表达式的类型被它的位置所暗示时,上下文类型就发生了。比如说:
window.onmousedown = function (mouseEvent) {
console.log(mouseEvent.button);
console.log(mouseEvent.kangaroo); // Ⓧ 在'MouseEvent'类型上不存在'kangaroo'属性。
};
2
3
4
在这里,TypeScript 类型检查器使用 window.onmousedown
函数的类型来推断赋值右侧的函数表达式的类型。当它这样做时,它能够推断出 mouseEvent 参数的类型,它确实包含一个按钮属性,但不包含袋鼠属性。
这样做的原因是 window 已经在其类型中声明了 onmousedown。
// 声明有一个名为'window'的全局变量
declare var window: Window & typeof globalThis;
// 这被声明为(简化版)。
interface Window extends GlobalEventHandlers {
// ...
}
// 其中定义了很多已知的处理程序事件
interface GlobalEventHandlers {
onmousedown: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null;
// ...
}
2
3
4
5
6
7
8
9
10
11
TypeScript 足够聪明,在其他情况下也能推断出类型:
window.onscroll = function (uiEvent) {
// Ⓧ 属性 "button" 不存在于 "Event"类型上。
console.log(uiEvent.button);
};
2
3
4
基于上述函数被分配给 Window.onscroll
的事实,TypeScript 知道 uiEvent 是一个 UIEvent,而不是像前面的例子那样是 MouseEvent。UIEvent 对象不包含按钮属性,所以 TypeScript 会抛出一个错误。
如果这个函数不在上下文类型的位置,这个函数的参数将隐含有类型 any,并且不会发出错误(除非你使用 noImplicitAny 选项)。
const handler = function (uiEvent) {
console.log(uiEvent.button); // <- 正确
};
2
3
我们也可以明确地给函数的参数提供类型信息,以覆盖任何上下文的类型。
window.onscroll = function (uiEvent: any) {
console.log(uiEvent.button); // <- 现在也没有错误
};
2
3
然而,这段代码将记录 undefined 的内容,因为 uiEvent 没有名为按钮的属性。
上下文类型化在很多情况下都适用。常见的情况包括函数调用的参数、赋值的右侧、类型断言、对象和数组字面量的成员,以及返回语句。上下文类型也作为最佳普通类型的候选类型。比如说:
function createZoo(): Animal[] {
return [new Rhino(), new Elephant(), new Snake()];
}
2
3
在这个例子中,最佳普通类型有一组四个候选者。Animal,Rhino,Elephant 和 Snake。其中,Animal 可以被最佳共同类型算法所选择。