typescript 类型层级
typeScript 中有一套类型兼容体系。对于每个类型,它们在这套体系中存在不同的位置。这位置就被抽象成,类型层级的概念。
什么是类型层级呢 ?
以下面一个例子来解释:
type Res = "acwink" extends string ? 1 : 2; // 1
这个例子中,通过条件语句来判断 "acwink"
这个字面量类型,是否满足 string
这个条件。如果满足则返回 1,否则返回 0。
通过类型层级来解释,其实就是判断 “acwink” 的类型 层级是比 string 类型层级低。通常情况下,类型层级高的类型,集合所包含的元素更多。所以,如果这个条件类型语句为 1,也就可以表示 "acwink"
这个字面量类型是 string 类型的 子类型(子集)。
用一个通用公式表示 A extends B 表示 A 属于 B的子集
。有了这个公式我们就能够很容易的判断类型层级了。
类型层级系统的样子 ?
我们自顶部向下来展开介绍。
Top Type 类型 any 和 unknown
在typescript 中定义了两个特殊的类型,any 和 unknown, 它们可以表示 ts 中的任何类型。也就是说
A extends any ? 1 : 2
这个条件语句始终是 1B extends unknown ? 1 : 2
这个条件语句始终是 1
any 和 unknown 可以是任何类型的父级,因为它们包含的范围更广阔,
值得我们注意的是,unknown 类型只能赋值给 any / unknown 类型,这是 ts 内部的设定。
同时,系统内部设定
any extends unkown ? 1 : 2; // 1
unkown extends any ? 1 : 2; // 1
Object 类型 和 object 类型
Object 类型表示的是TS中除了 any/unknown 类型以外的所有类型(原始类型、对象类型、函数类型等)。object 类型的出现是为了修复 Object 所包含原始类型的情况,object 类型表示的TS中除了Top Type 和 原始类型以外的所有类型。
首先,我们判断 Object、object 于 any 、unknown 的类型层级比较。
Object extends any ? 1 : 2; // 1
Object extends unknown ? 1 : 2; // 1
object extends any ? 1 : 2; // 1
object extends unknown ? 1 : 2; // 1
这里我们可以判断出,Object、object 是 any/unknown 的子类型。
但是,结果真的是如此吗。我们把条件对调一下。
any extends Object ? 1 : 2; // 1 | 2
unknown extends Object ? 1 : 2; // 2
any extends object ? 1 : 2; // 1 | 2
unknown extends object ? 1 : 2; // 2
神奇的情况出现了,按照上面对条件的语句的解释,any 即是Object子类型又是Object的父类型。由于TS的设定原因,这种情况只针对 any 类型出现,当 any 作为条件语句的待比较类型时,它的类型包含两部分,即让条件成立的一部分和让条件不成立的一部分。这很好理解,因为 any 包含TS中所有可能的类型。
为什么 unknown 就可以,不能像 any 那样呢。同样,让我们一句话解释 系统设定。
那么 Object、object 谁的类型层级更高呢 ?
Object extends object ? 1 : 2; // 1
object extends Object ? 1 : 2; // 1
同样,ts类型系统设定这两个类型相互兼容。
到此我们就可以得出类型层级:any/unknown > Object/object
包装类型
包装类型也就是String、Number、Boolean这一类的基础类型的对象类型的形式。
那么我们通过条件语句比较包装类型和对象类型Object、object的类型层级大小。
String extends object ? 1 : 2; // 1
String extends Object ? 1 : 2; // 1
以String 包装类型为例,我们可以得出结论,any/unknown > Object/object > 包装类型的类型层级。
基本数据类型
基本数据类型包括,string、number、boolean、undefined、null、void等。但是,这里的 undefined、null、void 是没包装类型的,这里我们暂时不参与类型层级比较。
所以接下来我们同样适用 string 进行和包装类型进行条件类型比较。
string extends String ? 1 : 2; // 1
String extends string ? 1 : 2; // 2
由此,我们可以得出结论类型层级 any/unknown > Object/object > String/Number/Boolean > string/number/boolean
但是,这里有个特殊情况
String extends {} ? 1 : 2; // 1
string extends String ? 1 : 2; // 1
这种情况是不是表明,{} 的类型层级比String更高呢,也就是看上去能满足如下条件语句 ?
string extends {} ? 1 : 2 // 1
但是,实际的结果其实是 2。那么这是为什么呢 ?
其实,在TS中有两套类型比较系统
- 基于类型信息的层面进行比较
- 基于结构化类型系统的比较
对于 extends {}
这样的结构,是使用结构类型系统的比较。我们可以把 String 看成一个,interface 声明的类型。从结构化类型的角度来看,String 就是 {} 的扩展, 也可以看成 {} 是 String的父类。
对于 {} extends
这样的结构,是使用的类型信息进行比较的,也就是说 {} 会被看成 Object/object 的字面量。
知道上面信息,我们可以将可以解释如下情况
{} extends object ? 1 : 2 // 1
object extends {} ? 1 : 2 // 1
{} extends Object ? 1 : 2 // 1
Object extends {} ? 1 : 2 // 1
字面量类型的联合类型
这里只争对基础数据类型 string、number、boolean来展开。以 string 的字面量类型组成的联合类型为例。
"acwink1" | "acwink2" extends string ? 1 : 2 // 1
这里 “acwink1” | “acwink2” 的每个元素都是 string 的字面量类型,那么,这么看起来 这个联合类型就是 string 这个类型的子类型。
这里可以得出结论:any/unknown > Object/object > String/Number/Boolean > string/number/boolean > 某一基础类型组成的联合类型
字面量类型
也就是原始类型的子类型呗
"acwink1" extends "acwink" | "acwink2" ? 1 : 2 // 1
这里可以得出结论:any/unknown > Object/object > String/Number/Boolean > string/number/boolean > 某一基础类型组成的联合类型 > 字面量类型(联合类型包含该字面量时)
Bottom Type Never
Never 代表虚无。为什么需要never存在呢,当我们求两个对象的交集。如果没有交集,那么就需要用never来表示。never 被 TS 设定为所有类型的子类型。
never extends A ? 1 : 2; // 1
A 是任何类型,这个条件语句都是恒成立的。
never有一个特点,不能被赋值。赋值则会报错,通常情况下,我们可以通过never的特性,来提示函数调用者参数传递错误。
never类型在联合类型中是不会被显示。
最后我们可以得出类型层级:any/unknown > Object/object > String/Number/Boolean > string/number/boolean > 某一基础类型组成的联合类型 > 字面量类型(联合类型包含该字面量时) > never