Last Surprise Lyn
  1. 1 Last Surprise Lyn
  2. 2 The Night We Stood Lyn
  3. 3 Warcry mpi
  4. 4 Life Will Change Lyn
  5. 5 Flower Of Life 发热巫女
  6. 6 Hypocrite Nush
  7. 7 かかってこいよ NakamuraEmi
  8. 8 Quiet Storm Lyn
  9. 9 Time Bomb Veela
  10. 10 One Last You Jen Bird
  11. 11 Libertus Chen-U
2022-10-04 22:43:31

类型体操基本功-相等判断

Typescript中判断相等或者是否属于某个类型的子类型的场景通常会用到extends,但在某些场景下,仅仅使用extends是不够的。这篇文章就借着之前遇到的一个案例简单拓展介绍一下。

引子

实现一个AnyOf泛型,接收一个数组,如果数组中任一个元素为真,则返回true,否则返回false。

type Sample1 = AnyOf<[1, '', false, [], {}]> // expected to be true.
type Sample2 = AnyOf<[0, '', false, [], {}]> // expected to be false.

一个比较容易想到的思路就是对传入的数组递归去判断,得到下面写法。

type AnyOf<T extends readonly any[]> = T extends [infer F, ...infer R]
  ? F extends 0 | '' | false | [] | undefined | null | {}
    ? AnyOf<R>
    : true
  : false

但这样通不过大多数测试用例,原因就在{}这个空对象上。如果使用extends去判断的话,下面的用例都和我们的预期——只有{}返回true不一样。

type a = true extends {} ? true : false; // true
type b = {'a': 1} extends {} ? true : false; // true

看起来在Typescript中{}这个对象比较特殊,不能用extends表示相等的关系。所以需要寻求另外的办法。正好官方在这个Issues下提供了一个关于Equal工具泛型的实现。

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

让我们基于这个工具泛型改造下上面的AnyOf实现方案试试。

type AnyOf<T extends readonly any[]> = T extends [infer F, ...infer R]
  ? F extends 0 | '' | false | [] | undefined | null
    ? AnyOf<R>
    : (
      Equal<F, {}> extends true ? AnyOf<R> : true
    )
  : false

成功通过测试案例。

Equal实现原理

如果要我们自己实现一个Equal的话,最容易想到的还是正反判断extends。

 type MyEqual<X, Y> = X extends Y ? (Y extends X ? true : false) : false

理论上看是正确的,但忽略了Typescript中交叉类型的存在。

type A = {
    name: string,
    age: number
}
type B = {
    name: string
} & {
    age: number
}

type error = MyEqual<A, B> // true, expected to false

我们再回头看Equal的实现。

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

很难理解,为什么判断两个类型相等还要构造两个函数。stackoverflow里有人提问了,答案有一篇长文说的比较明白,问题跳转

简单来说就是含extends处理过的条件类型必须要求拓展后的类型相同才可分配,否则会出现一些问题。Equal的实现就是构造了两个条件类型,这两个条件类型具体实现并不重要,只需要结构相同。仅当X和Y全等时,条件类型才可以通过extends再判断相等。比如换成下面这样的实现也是可行的。

type Equal<X, Y> =
  (<T>() => T extends [X] ? string : number) extends
  (<T>() => T extends [Y] ? string : number) ? true : false

Alike

说完了Equal,进一步的,我们如果不判断严格相等,仅字段相同就相等的话就可以用Alike这个工具泛型。

export type MergeInsertions<T> = T extends object ? { [K in keyof T]: MergeInsertions<T[K]> } : T

export type Alike<X, Y> = Equal<MergeInsertions<X>, MergeInsertions<Y>>

实现也比较简单,经过MergeInsertions合并交叉类型到一个类型中,再进行全等比较。

小结

综上可以总结,当在Typescript中做子集比较时,通常用extends,如果要做宽松相等的比较用Alike,严格相等的比较用Equal,一般就能覆盖大多数需要做类型比较的场景了。

-- EOF --

添加在分类「 前端开发 」下,并被添加 「TypeScript」 标签。