类型推导

状态: 初稿

介绍

在这一章, 我们将探讨 TypeScript 类型推导机制. 包括两个主题: 1. 类型推导在何处发生, 2. 具体推导方法.

基础

在 TypeScript 中, 类型推导的作用是为缺少显式类型注解的语境补全类型信息.
考虑这个例子

1
let x = 3;

类型推导推出变量 x 的类型是 number.
这种类型的推导发生在初始化变量和成员变量时, 指定函数参数默认值时, 以及定出函数返回值类型时.

在多数情况下, 类型推导足够简单直接.
以下各节. 我们将更细致地探索类型推导方法.

最佳公共类型

如果类型推导的输入是多个表达式, 它需要从这些子表达式的类型中找出一个 “最佳公共类型”. 如:

1
let x = [0, 1, null];

要推导 x, 我们必须考虑每个数组元素类型.
易知, 共有 number[]null[] 两种选择.
“最佳公共类型”算法考虑每个候选类型, 选出与所有候选相容的那一个.

由于算法从所有候选类型中挑选最佳公共类型, 有种情况是所有候选类型共享一部分结构, 但是没有一个类型是所有类型的父类型. 例如:

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

zoo 理想推导结果应该是 Animal[], 但数组中没有一个元素是严格意义上的 Animal, 类型推导无法给出我们想要的结果.
此时, 最好显式为 zoo 提供类型信息:

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

上下文类型

类型推导有时也反方向地进行.
这叫做”上下文类型”. 上下文类型推导的依据是表达式的类型已由它所处位置体现. 例如:

1
2
3
4
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.button); //<- OK
console.log(mouseEvent.kangaroo); //<- Error!
};

这里, TypeScript 类型检查器根据类型已知的 window.onmousedown 成员自动推导赋值号右侧的函数表达式.
函数参数 mouseEvent类型随函数表达式类型的确定而明确下来, 该参数有 button 属性, 没有 kangaroo 属性.

换个场景, TypeScript 同样聪明:

1
2
3
window.onscroll = function(uiEvent) {
console.log(uiEvent.button); //<- Error!
}

基于以上函数表达式赋值给 window.onscroll 的事实, TypeScript 知道 uiEvent 的类型是 UIEvent, 而不再前一个例子中的 MouseEvent. 因为 UIEvent 没有 button 属性, TypeScript 将报错.

如果函数所处位置不含上下文类型信息, any 隐式地成为所有函数参数的类型, 编译器不会报错(除非你开启 --noImplicitAny 选项).

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

我们可以显式写下函数参数类型, 从而阻止上下文类型推导:

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

然而, 由于 uiEvent 实际上没有叫 button 的属性, 这个函数会输出 undefined.

上下文类型覆盖 TypeScript 程序的方方面面.
常见的有函数实参, 右值, 类型担保, 类成员和数组字面量, return 语句.
上下文类型推导丰富了最佳公共类型的候选. 对于下例:

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

在这个例子中, 最佳公共类型有四个候选: Animal, Rhino, Elephant, 和 Snake.
我们知道, 现在最佳公共类型算法可以选择 Animal.