《TypeScript实战指南》—2.2.8 类型推导

举报
华章计算机 发表于 2019/06/16 12:46:00 2019/06/16
【摘要】 本节书摘来自华章计算机《TypeScript实战指南》一书中的第2章,第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的使用。了解类型推导主要是为了使我们能动态地获取类型,减少手动标注类型的工作量,提升效率。这在后续的实战章节中会详细介绍。


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。