《TypeScript实战指南》—2.2.8 类型推导
2.2.8 类型推导
讲了那么多类型,让我们回到类型推导上来。
简而言之,类型推导就是在没有明确指出类型的地方,TypeScript 编译器会自己去推测出当前变量的类型。
例如下面的例子:
let a = 1;
我们并没有显示地指明 a 的类型,所以编译器需要自己通过结果反向推测变量 a的类型是 number。这种推断发生在变量初始化或者函数有返回值时。
大多数情况下,类型推导是这样直截了当的。但也有很复杂的情况,比如像我们上面一节所讲的,需要去匹配参数来推测类型。
当需要从几个表达式中推断类型时,会使用这些表达式的类型来推断出一个最合适的通用类型集。例如:
let a = [0, 'hello', null]; // (string | number)[]
为了推断a的类型,我们必须考虑所有元素的类型。这里有三种选择:number、string和null。计算通用类型算法会考虑所有的候选类型,并给出一个兼容所有候选类型的类型,这个例就是 (string | number)[] 。
TypeScript里的类型兼容性是基于结构子类型的,如下所示:
interface Person{
age: number;
}
class Father{
age: number;
}
let person: Person;
person = new Father();
我们可以看到在以上的类型中,只要满足了子结构的描述,那么它就可以通过编译时检查。所以 TypeScript的设计思想并不是满足正确的类型,而是满足能正确通过编译的类型。这就造成了运行时和编译时可能存在类型偏差。
所以TypeScript的类型系统允许某些在编译时无法进行安全确认的类型操作。当一个类型系统具有此属性时,被认为是“不可靠”的。而TypeScript允许这种行为是经过仔细考虑的。下面我们会解释为什么需要这种特性。
TypeScript结构化类型系统的基本规则是,如果x要兼容y,那么y至少具有与x相同的属性。例如:
interface Person {
name: string;
}
let person: Person;
const alice = { name: 'Alice', age: 22 };
person = alice;
当alice赋值给person时,编译器会去检查person中的每个属性,看是否能在alice中也找到对应的属性。编译器发现alice中有name属性,那它就是合理的,即便事实上并不准确。
检查函数参数时也使用了相同的规则,例如:
function greetTo(person: Person) {
console.log('Hello, ' + person.name);
}
greetTo(alice); // OK
注意,alice 有个 name属性,但并不会引发 TypeScript 报错,因为Type Script只会检查是否符合 Person 的类型标准。
这个比较过程是递归进行的,检查每个成员及子成员。
相对来讲,在比较原始类型和对象类型的时候是容易理解的;而在判断两个函数返回值是否相同时,TypeScript 比对的是函数签名。
一个函数里面包含了参数及返回值,我们可以看一看下面这个例子:
let fun1 = (a: number) => 0;
let fun2 = (b: number, s: string) => 0;
fun2 = fun1;
fun1 = fun2;
// [ts] Type '(b: number, s: string) => number' is not assignable to type '(a: number) // => number'.
要查看fun1是否能赋值给fun2,首先看它们的参数列表。fun1的每个参数必须能在fun2里找到对应类型的参数。注意,参数的名字相同与否无所谓,只看它们的类型。这里,fun1的每个参数在fun2中都能找到对应的参数,所以赋值是允许的。
fun1 = fun2会出现赋值错误,因为fun2 有第二个必填参数,但是fun1并没有,所以不允许赋值。
你可能会感到疑惑,为什么允许fun1忽略参数?
实际上在 JavaScript 世界中参数忽略是很常见的。比如 Array.map和Array.forEach,我们并不好要求用到每个参数。
我们再来看一个更复杂的情况,叫 ReturnType,这个类型也是写在 typescript/lib/lib.es5.d.ts 中:
/**
* Obtain the return type of a function type
*/
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
ReturnType 的使用是这样的:
let fun1 = (a: number) => ({ a, b: 'hello' });
type Fun1ReturnType = ReturnType<typeof fun1>
// type Fun1ReturnType = {
// a: number;
// b: string;
// }
infer 关键字可以帮助我们引入一个待推断的类型变量,这个待推断的类型变量在推断成立时会写入类型;而在失败时会回退为 any。
到目前为止,我们简单地梳理了一遍 TypeScript 的类型推导,也初步了解了 infer的使用。了解类型推导主要是为了使我们能动态地获取类型,减少手动标注类型的工作量,提升效率。这在后续的实战章节中会详细介绍。
- 点赞
- 收藏
- 关注作者
评论(0)