type-challenges 中的 Equal<X, Y> 是如何实现类型比较的

180 阅读2分钟

type-challenges/type-challenges 中的 Equal<X, Y> 类型是如何实现类型比较的

在仓库中查看文件的 history,发现有两个历史版本:

  1. 初始提交
  2. 目前使用(after 这个提交

自己先试一试如何实现类型比较

单次 extends

我一开始想的类型的比较,最简单就是使用 a extends b,但是很明显的 fail case 就是:

type SingleExtends<A, B> = A extends B ? true : false
type FAIL_CASE_OF_SingleExtends = SingleExtends<1, number>

双次 extends

那使用两次 extends 吧,很遗憾,也有 fail case:

type DoubleExtends<A, B> = A extends B ? B extends A ? true : false : false
type FAIL_CASE_OF_DoubleExtends = DoubleExtends<true, boolean> // boolean

看出这里有个知识点,UnionType extends TypeUnionType 会被逐个拆开进行计算。

Functional extends

想起关于 TypeScript 中关于协变和逆变的概念,把要比较的类型放到箭头函数的参数中。居然还是有 fail case,哭了。

// type FunctionExtends<A, B> = SingleExtends<(arg: A) => A, (arg: B) => B>
type FunctionExtends<A, B> = ((arg: A) => A) extends ((arg: B) => B) ? true : false

// 简直恶搞
type FAIL_CASES_OF_FunctionExtends = [
  FunctionExtends<{ readonly a: string }, { a: string }>,
  FunctionExtends<{ a: string }, { readonly a: string }>,
  FunctionExtends<any, unknown>,
  FunctionExtends<unknown, any>,
] // [true, true, true, true]

我知道的就这么多了,我想不出来其它了。

初始版本的 Equal

→源码←

看了下,看出了 NOT_EQUAL_INTERNAL 的双次 extends,最终还是依赖 UnionToIntersection

但也没理解 UnionToIntersection 的意思,这里是有 fail cases 的:

type UnionToIntersection<U> = (U extends any ? (k: U) => void : 2) extends (k: infer I) => void ? I : 1

type case1 = UnionToIntersection<1 | 2> // I 是 never,就是不可能有这样的类型。
type case2 = UnionToIntersection<never> // unknown,冷知识:never extends 任何类型,居然都是 true 的。

UnionToIntersection 有 fail case 了,自然而然,这个版本的 Equal 也有 fail case 了。

目前使用的 Equal

→源码←

不知道是什么知识点,反正 antfu 就直接改这样了,提交信息也没看出要点:

type Equal<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false

大概陈述一下:

  1. 用的还是 single extends;
  2. 其次被比较的类型,最妙的就是这里, <T>() => T extends X ? 1 : 2,这个表达式不会计算出结果,所以真正比较的是两个 extends 表达式(类似 Functional extends ?),估计核心就是这里了
  3. 12 没有别的意思,就是凑数的,换其它都行。