类型体操基本功-相等判断
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」
标签。