IsEqual
IsEqual
定义
俺们对于严格相等的定义是: 两个类型必须完全相同.
注意
俺们这里说的不是"可分配性", 而是"完全相同".
例如, 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