IsEqual
IsEqual
定义
俺们对于严格相等的定义是: 两个类型必须完全相同.
Warning
俺们这里说的不是"可分配性", 而是"完全相同".
例如, string和string是相同的, string和'sometext'是不同的.
引入
对于判断两个类型是否相等, 很多读者可能会想到使用extends操作符, 通过两个类型互相可分配来描述其相等, 但是这种方式并不严格, 例如:
type IsEqual<T, U> = T extends U ? U extends T ? true : false : false这种方式在很多情况下是可以工作的, 但是在一些特殊情况下, 它并不能很好的工作, 例如:
type X = IsEqual<{ x: any }, { x: number }>这里X的结果是true, 但是这两个类型并不相等.
也就是说,
互可分配并不代表相等.
Mutually assignable does not mean equal.
因为 any是特殊的. 在TypeScript中, any是一个特殊的类型, 它可以被分配给任何类型, 也可以接受任何类型. (除了never)
另外, 对于联合类型, 这里还会出现问题, 例如:
type A = 1 | 2
type B = 1 | 2
type X = IsEqual<A, B> // boolean这里错误的推导出了X是boolean, 但是A和B是相等的, 俺们期待的结果是true. 这与TypeScript的extends对于联合类型的分发特性有关.
更加进阶的读者可能会想到使用下面的方式:
type IsEqual<T, U> = [T] extends [U] ? [U] extends [T] ? true : false : false俺们把T, U放进元组, 由于元组是不可变的, 这样就可以避免联合类型的分发特性. 但是这种方式并不完美, 只有一个问题, 即在这个情况下, any会与任何类型相等(除了never), 这并不是俺们想要的, 比如:
type s = IsEqual<any, number> // true俺们期望的结果是false, 因为any和number并不严格的相等.
解答
俺们使用
type Equals<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;这里利用了extends在检查函数签名时的特性, 他们将在所有的T下相等, 这要求X和Y严格相等(identical).
设想这样的场景:
type X = any
type Y = number
type s =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false对于任意的T, (<T>() => T extends X ? 1 : 2)是否总是 extends (<T>() => T extends Y ? 1 : 2)呢?
设想以下情形:
// T is any
() => any extends X ? 1 : 2 extends () => any extends Y ? 1 : 2 is true
// T is number
() => number extends X ? 1 : 2 extends () => number extends Y ? 1 : 2 is true
// T is string, counter example
() => string extends X ? 1 : 2 extends () => string extends Y ? 1 : 2 is false存在这样的T, 使得(<T>() => T extends X ? 1 : 2)不等于(<T>() => T extends Y ? 1 : 2), 所以X和Y不相等.
// 让俺们仔细观察这个反例
// 让俺们代入 T is string, X is any, Y is number
// 俺们求解第二个extends左右两项的类型
() => string extends any ? 1 : 2 is 1
() => string extends number ? 1 : 2 is 2
// 求解第二个extends的结果
1 extends 2 is false那么是否当且仅当X和Y严格相等时, (<T>() => T extends X ? 1 : 2)才等于(<T>() => T extends Y ? 1 : 2)呢?
数学证明
为了便于书写, 俺们约定以下的写法:
Eq<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false
// 等价于
declare let x: <T>() => (T extends number ? 1 : 2)
declare let y: <T>() => (T extends number ? 1 : 2)
y = x // x是否能够安全的赋值给y?上面的问题又等价于:
假设是所有类型的集合,
, 下面的等式是否成立:
Proof:
于是俺们得到了结论, Equals可以判断两个类型是否严格相等.
这对于any和联合类型都是有效的.
type s = Equals<any, number> // false参考
https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650
