Typescript学习(九) 类型层级

146 阅读7分钟

概念

在Typescript中, 我们之所以会得到各种类型的提示, 是因为有兼容性这个限制条件存在, 不兼容就会提示类型错误; 那么什么是不兼容? 凭什么说一个类型不兼容另一个类型呢? 这背后起作用的其实就是一个类型层级系统, 也可以说各个类型之间的父子关系, 这种关系的表现就是:

  1. 因为b是a的子类型, 所以a = b成立;

  2. 因为a是b的子类型, 所以a extends b 成立, 则说明

// 赋值比较
let name:'jack' = 'jack'
let str:string = name // 正确
name = str // 报错

// extends条件类型比较
type result1 = 'jack' extends string ? 'true' : 'false' // 'true'
type result2 = string extends 'jack' ? 'true' : 'false' // 'false'

所以, 所谓的类型层级, 其实可以看作就是兼容性背后的规则!

而类型的层级, 之所以称之为'层级', 就是因为不同的类型存在一种整体上的'高低层级关系', 有的类型可以看作所有类型的父类型, 比如any/unknown, 因为所有类型都可以赋值给它们! 而有的类型处于'最底层'的, 比如never, 理由就是, 所有类型都不能赋值给它, 除了它自己; 而Typescript中的类型很多, 在这一个顶端, 一个底端之间, 还有很多类型, 我们就来探讨下这中间的部分;

字面量类型

我们先从字面量类型和原始类型开始, 因为这是最常用, 最好理解的部分, 我们知道 原始类型的字面量类型是其所对应的原始类型的子类型

type isSon<T, U> = T extends U ? 'yes' : 'no'

type result1 = isSon<'jack', string> // 'yes'
type result2 = isSon<1, number> // 'yes'
type result3 = isSon<true, boolean> // 'yes'
type result4 = isSon<string, 'str'> // 'no'

可以得出结论, 那就是字面量类型, 在类型层级系统中, 肯定在其对应的原始类型之下: 字面量类型 小于 其所对应的原始类型

联合类型

但是, 字面量类型和原始类型之间是否还有其他类型呢? 答案是肯定的, 别忘了联合类型, 之前我们说过, 只要一个类型是存在于另一个联合类型中, 就可以看作是这个联合类型的子类型

type result1 = string extends string | number | symbol ? 'yes' : 'no' // yes
type result2 = number extends string | number | symbol ? 'yes' : 'no' // yes
type result3 = symbol extends string | number | symbol ? 'yes' : 'no' // yes

看完以上案例, 我们是否能说, 原始类型就是联合类型的子类型呢? 这么说显然不严谨, 只能说, 如果一个联合类型的成员包含了另一个原始类型, 才能说这个原始类型是这个联合类型的子类型, 此时 字面量类型 小于 原始类型 小于 联合类型; 但是看看下面的案例:

type result1 = 'a' | 'b' | 'c' extends string ? 'yes' : 'no' // yes
type result2 = 1 | 2 | 3 extends number ? 'yes' : 'no' // yes

这样, 联合类型反而成了原始类型的子类型了, 当然, 这其中也是有条件的: 联合类型的所有成员都隶属于一个原始类型; 只有这种情况下, 才能说 字面量类型 小于 联合类型 小于 原始类型

综合一句, 联合类型和原始类型之间的关系, 随着具体条件变化而变化;

对象类型

说完了字面量类型, 联合类型, 原始类型, 再来看看对象类型, 对象类型由包括object, 一些装箱类型, 比如: String, Number, Object等等; 我们知道, 原始类型是与之对应的装箱类型的子类型:

type result1 = string extends String ? 'yes' : 'no' // yes
type result2 = String extends string ? 'yes' : 'no' // no

type result3 = number extends Number ? 'yes' : 'no' // yes
type result4 = Number extends number ? 'yes' : 'no' // no

同时, 装箱类型又是Object的子类型

type result1 = String extends Object ? 'yes' : 'no' // yes
type result2 = Object extends String ? 'yes' : 'no' // no

由此我们可以得出: string是装箱类型String的子类型, String又是Object的子类型

type result1 = string extends String ? 'yes' : 'no' // yes
type result2 = String extends Object ? 'yes' : 'no' // yes

所以, string是Object的子类型

type result1 = string extends Object ? 'yes' : 'no' // yes

好的, 这套逻辑很顺, 我们再来看下一个例子:

type a = string extends String ? 'yes' : 'no' // yes
type b = String extends {} ? 'yes' : 'no' // yes
type c = {} extends object ? 'yes' : 'no' // yes

根据之前的逻辑, 既然 string是String的子类型, String是{}的子类型, {}又是object, 那么string自然是object的子类型喽? 然而, 结果并不是:

type d = string extends object ? 'yes' : 'no' // no

这是为什么呢? 之前我们的string 小于 String 小于 Object, 是因为它们的继承逻辑都是一致的, string只表示字符串, 它是由String构造函数创造出来的, Object也是一个创造一切的构造函数, 所以这条继承逻辑走得通; 再来看看String extends {} 和 {} extends object, 注意这两者的继承逻辑是不同的

  1. String extends {}之所以能成立, 是因为从结构化类型的角度来看, String可以看作是一个包含了很多属性的对象, 里面有startsWith、substring等等方法; 一个包含了很多属性的对象, 在结构化类型体系下, 自然是空对象的子类型;
  2. 而{} extends object, 是从类型信息的角度来判断的, 从这个角度来讲, {}作为一个object的字面量, 具备更加具体的信息, 属于object的子类型;

所以, 这条继承链可以认为是'断了'的, 这就好比, 你从北京做高铁来广州, 再转坐地铁到了广州塔, 但是肯定不能因此就说, 你能从北京直接坐高铁来广州塔!

好了, 回归正题, 说完了这个特殊情况之后, 我们可以得出结论, 那就是 原始类型 小于 装箱类型 小于 Object

any&unknown

Object已经可以说是目前的最高的类型层级了, 那么, 还有比它更高的吗? 当然有, 正如本节开头说的, 还有any、unknown这俩兄弟; 它们的区别, 在前面已经讲了, 要把它们赋值给别的类型, 它俩是有'分歧'的: any是啥意见都没有, unknown则是只能赋给它自己和any; 但是, 要说到别人赋值给它们, 它俩是'意见一致'的, 所以, 它们可以看作是类型层级的顶点了! 即 top type

any和unknown可以看作是所有类型的父类

any:

type result1 = string extends any ? 'yes' : 'no' // yes
type result2 = number extends any ? 'yes' : 'no' // yes
type result3 = Object extends any ? 'yes' : 'no' // yes
type result4 = object extends any ? 'yes' : 'no' // yes
type result5 = {} extends any ? 'yes' : 'no' // yes

unknown:

type result1 = string extends unknown ? 'yes' : 'no' // yes
type result2 = number extends unknown ? 'yes' : 'no' // yes
type result3 = Object extends unknown ? 'yes' : 'no' // yes
type result4 = object extends unknown ? 'yes' : 'no' // yes
type result5 = {} extends unknown ? 'yes' : 'no' // yes

但是, 有一点要注意, 那就是any也可以作为其他类型的子类型, 即赋值给其他类型, 那我们调换过来看看效果:

type result1 = any extends string ? 'yes' : 'no' // "yes" | "no"
type result2 = any extends number ? 'yes' : 'no' // "yes" | "no"
type result3 = any extends Object ? 'yes' : 'no' // "yes" | "no"
type result4 = any extends object ? 'yes' : 'no' // "yes" | "no"
type result5 = any extends {} ? 'yes' : 'no' // "yes" | "no"

type result6 = any extends any ? 'yes' : 'no' // "yes"
type result7 = any extends unknown ? 'yes' : 'no' // "yes"

我们会发现, any extends 除了any自己和unknown, 其他的竟然全部都是联合类型, 即 '是, 也不是', 这里说白了, 也是系统一个'特殊的设定', 而且any属于包罗万象, 又可以转为其它对象, 所以, 返回的是一个联合类型;

到了这里, 我们就可以得出结论: Object 小于 any&unknown

never

正如开头说的, 还有最底层的类型, 即never类型, 任何类型都无法赋值给它, 除了它自己;

type result1 = 'hello' extends never ? 'yes' : 'no' // no
type result2 = never extends 'hello' ? 'yes' : 'no' // yes

let neverData:never

let str:string = neverData

neverData = str // 报错

我们很容易得出结论: never 小于 字面量类型

层级链

由此, 我们可以从never到any/unknown之间 建立一个层级链

type typeLine =
never extends 123
? 123 extends 123 | 456 | 789
? 123 | 456 | 789 extends number
? number extends Number
? Number extends {}
? {} extends object
? object extends {}
? object extends Object
? Object extends any
? Object extends unknown
? any extends unknown
? unknown extends any
? 12
: 11
: 10
: 9
: 8
: 7
: 6
: 5
: 4
: 3
: 2
: 1
: 0