我在以下位置找到了一个Equals
实用程序:https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650
export type Equals<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;
它可用于检查两种类型是否相等,例如:
type R1 = Equals<{foo:string}, {bar:string}>; // false
type R2 = Equals<number, number>; // true
我很难理解这是如何工作的,以及表达式中的T
是什么意思。
有人能解释一下吗?
1条答案
按热度按时间6gpjuf901#
首先,让我们添加一些括号
现在,当你用一些类型替换
X
和Y
时,第二个extends
关键字基本上是在问一个问题:“<T>() => (T extends X ? 1 : 2)
类型的变量是否可赋值给(<T>() => (T extends Y ? 1 : 2))
类型的变量?换句话说你提供的评论的作者说
条件类型的可赋值性规则<...>要求
extends
之后的类型与检查器定义的类型“相同这里他们讨论的是第一个和第三个
extends
关键字,检查器只允许x
可以赋值给y
,前提是它们后面的类型,即X
和Y
,是相同的。当然这不应该是错误,因为有两个相同类型的变量,现在如果你用
number
替换X
,用string
替换Y
现在
extends
之后的类型不相同,因此会出现错误。现在让我们看看为什么
extends
后面的变量类型必须相同才能赋值。如果它们相同,那么一切都应该很清楚,因为你只有两个相同类型的变量,它们总是可以彼此赋值的。至于另一种情况,考虑我描述的最后一种情况,Equals<number, string>
。假设这不是一个错误请考虑以下代码片段:
如果类型不是
string
和number
也是类似的,这两个类型没有任何共同点,但更复杂。让我们尝试将{foo: string, bar: number}
指定为X
,将{foo: string}
指定为Y
。注意,这里X
可赋值给Y
如果您切换类型,并尝试
{foo: string}
来替换X
,尝试{foo: string, bar: number}
来替换Y
,那么调用y<{foo: string}>()
将再次出现问题,您可以看到总是有错误。更准确地说,如果
X
和Y
不相同,总会有一些类型扩展其中一个,而不扩展另一个。如果你试图为T
使用这种类型,你会得到一个无意义的。实际上,如果你试图为y = x
赋值,编译器会给你一个如下的错误:因为总是有一个类型可以赋值给
X
和Y
中的一个,而不能赋值给另一个,所以它被迫将x
的返回类型视为1 | 2
,而1 | 2
不能赋值给T extends ... ? 1 : 2
,因为T
可以扩展这个...
,也可以不扩展。这基本上就是
Equals
类型的本质,希望它或多或少地清楚它是如何工作的。UPD 2:我想添加另一个简单的例子,其中一个简单的相等性检查失败,但是
Equals
没有。备注:如果你想更细致,理论上
A
也应该是false
,因为{} extends {a?: number}
应该是false
(不是所有{}
类型的变量都可以赋值给{a?: number}
类型的变量),但是TS并不像它声称的那样“100%正确”,所以在TS中这是true
。例如,类型
{a: string}
可赋值给{}
,但不能赋值给{a?: number}
,因此当您使用它时,会收到一个错误:UPD 1:
说到为什么
Equals<{x: 1} & {y: 2}, {x: 1, y: 2}>
是false
tl;dr据我所知,这是一个实现细节(不确定我是否应该称之为bug,这可能是故意的)
当然,理论上应该是
true
,正如我前面所述,Equals
返回false
(理论上)当且仅当存在类型X1 M62 N1 X使得X1 M63 N1 X可分配给X1 M64 N1 X和X1 M65 N1 X之一,但另一个没有,在上面的例子中,如果你执行x = y
并插入(x<C>()
和y<C>()
),你会得到错误的输入。然而,在这里,情况不是这样的,所有可以赋值给{x: 1} & {y: 2}
的东西都可以赋值给{x: 1, y: 2}
,所以理论上Equals
应该返回true
。然而,实际上,在判断类型是否相同时,typescript的实现似乎采取了一种更懒惰的方法。我应该指出,这是一种猜测,我从未对typescript做出过贡献,也不知道它的源代码,但这是我在过去10分钟内发现的,我可能完全错过了一些细节,但这个想法应该是正确的。
ts存储库中执行类型检查的文件是checker.ts(链接指向ts 4.4和4.5之间的文件版本,将来可能会更改)。此处的
19130
行似乎是比较T extends X ? 1 : 2
和T extends Y ? 1 : 2
部分的位置。以下是相关部分:注解说这些类型是相关的,如果在其他条件中,
U1
和U2
,在我们的例子中X
和Y
,是相同的,这正是我们要检查的。在19143
行,你可以看到extends
后面的类型正在被比较,这导致isTypeIdenticalTo
函数,它依次调用isTypeRelatedTo(source, target, identityRelation)
:你可以看到,首先它检查它们是否完全相同的类型(就ts实现而言,
{x: 1} & {y: 2}
和{x: 1, y: 2}
不是相同的类型),然后它比较它们的flags
.如果你看一下这里Type
类型的定义,你会发现flags
是这里定义的TypeFlags
类型,你会看吗:交集有自己的标志,所以{x: 1} & {y: 2}
有Intersection
的标志,{x: 1, y: 2}
没有,所以它们不相关,所以Equals
返回false
,尽管理论上它不应该返回。