TS 类型体操

类型体操: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