类型体操:type gymnastics
extends
extends 的含义是「可赋值」,即如果 A extends B 则有 b: B = a as A 成立
我们也可以从集合的角度理解,将 extends 当成「包含于 ⊆」的含义,即 A extends B 则意味着 A ⊆ B
never, any, unknown
我们可以认为,在集合的角度,never ⊆ 任何类型 ⊆ unknown
never 是底类型(bottom type):它是所有类型的子类型
unknown 是顶类型(top type):所有类型(包括 any)都可赋值给 unknown
所以,存在如下的性质
never extends 任意值都是成立的
type X1 = never extends never ? 'yes' : 'no'; // yes type X2 = never extends 233 ? 'yes' : 'no'; // yes type X3 = never extends unknown ? 'yes' : 'no'; // yes
任意(非 never)值 extends never 都是不成立的
type X4 = 233 extends never ? 'yes' : 'no'; // no type X5 = unknown extends never ? 'yes' : 'no'; // no
任意值 extends unknown 都是成立的
type X6 = never extends unknown ? 'yes' : 'no'; // yes type X7 = 233 extends unknown ? 'yes' : 'no'; // yes type X8 = unknown extends unknown ? 'yes' : 'no'; // yes
unknown extends 任意(非 unknown)值都是不成立的
type X9 = unknown extends never ? 'yes' : 'no'; // no type X10 = unknown extends 233 ? 'yes' : 'no'; // no
在这基础上,还有个特殊的 any;any 不太适合从集合角度理解,我们回归到「可赋值」角度
任何值都可以赋值给 any
type Y1 = never extends any ? 'yes' : 'no'; // yes type Y2 = 233 extends any ? 'yes' : 'no'; // yes type Y3 = unknown extends any ? 'yes' : 'no'; // yes type Y4 = any extends any ? 'yes' : 'no'; // yes
any 可以赋值给 unknown
type Y5 = any extends unknown ? 'yes' : 'no'; // yes
特殊规则:当被检查的类型是 any 时,条件类型会取两条分支的并集
type Y6 = any extends never ? 'yes' : 'no'; // yes | no type Y7 = any extends 233 ? 'yes' : 'no'; // yes | no
条件类型展开 / IsNever
根据 extends 的性质,显而易见
type X = never extends never ? 'yes' : 'no'; // yes
但如果换种写法呢?
type IsNever<T> = T extends never ? 'yes' : 'no'; type X = IsNever<never>; // never
TS 中存在一个 Distributive Conditional Types 规则,即对于 type T = T1 | T2 这种集合类型,DoSomething<T> 会被转换成 DoSomething<T1> || DoSomething<T2>
而 never 会被认为是一个空的集合类型,因此 DoSomething<never> 始终为 never
那么,正确的 IsNever 实现就需要「关掉」这个展开的特性
type IsNever<T> = [T] extends [never] ? true : false; type IsNever<T> = (() => T) extends () => never ? true : false;
参考:https://github.com/microsoft/TypeScript/issues/31751
IsUnknown
参考:https://github.com/microsoft/TypeScript/issues/27418
IsEqual
利用集合的性质,A ⊆ B 且 B ⊆ A 则 A = B
再结合上面 IsNever 的经验(关掉对集合类型的展开)
infer
infer 可以让我们从表达式中「提取」类型,有点类似 Rust 的 match pattern
例如我们想定义一个 Flatten 类型,将 Array<T> 恢复为 T
不使用 infer 的做法是
type Flatten<T> = T extends Array<any> ? T[number] : never
而利用 infer,我们可以直接「匹配并提取」中间的 any
type Flatten<T> = T extends Array<infer Item> ? Item : never