typescript - `Equals` 在打字稿中是如何工作的?
问题描述
我在以下位置找到了一个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
表达式中的含义。
有人可以解释一下吗?
解决方案
首先让我们添加几个括号
export type Equals<X, Y> =
(
(<T>() => (T extends /*1st*/ X ? 1 : 2)) extends /*2nd*/
(<T>() => (T extends /*3rd*/ Y ? 1 : 2))
)
? true
: false;
现在,当您替换某些类型时,X
第二Y
个extends
关键字的作用基本上是在问一个问题:“类型的变量可以<T>() => (T extends X ? 1 : 2)
分配给类型的变量(<T>() => (T extends Y ? 1 : 2))
吗?换句话说
declare let x: <T>() => (T extends /*1st*/ X ? 1 : 2) // Substitute an actual type for X
declare let y: <T>() => (T extends /*3rd*/ Y ? 1 : 2) // Substitute an actual type for Y
y = x // Should this be an error or not?
您提供的评论的作者说
条件类型 <...> 的可分配性规则要求后面的类型
extends
与检查器定义的类型“相同”
在这里,他们谈论的是第一个和第三个extends
关键字。如果它们之后的类型(即和)相同,检查器将只允许x
分配给。如果你同时替换两者y
X
Y
number
declare let x: <T>() => (T extends number ? 1 : 2)
declare let y: <T>() => (T extends number ? 1 : 2)
y = x // Should this be an error or not?
当然这不应该是一个错误,因为有 2 个相同类型的变量。现在如果你number
用X
和string
代替Y
declare let x: <T>() => (T extends number ? 1 : 2)
declare let y: <T>() => (T extends string ? 1 : 2)
y = x // Should this be an error or not?
现在后面extends
的类型不一样了,所以会报错。
现在让我们看看为什么后面的类型extends
必须相同才能使变量赋值。如果它们相同,那么一切都应该清楚,因为您只有两个相同类型的变量,它们总是可以相互分配的。至于另一种情况,请考虑我描述的最后一种情况,使用Equals<number, string>
. 想象一下这不是一个错误
declare let x: <T>() => (T extends number ? 1 : 2)
declare let y: <T>() => (T extends string ? 1 : 2)
y = x // Imagine this is fine
考虑这个代码片段:
declare let x: <T>() => (T extends number ? 1 : 2)
declare let y: <T>() => (T extends string ? 1 : 2)
const a = x<string>() // "a" is of type "2" because string doesn't extend number
const b = x<number>() // "b" is of type "1"
const c = y<string>() // "c" is of type "1" because string extends string
const d = y<number>() // "d" is of type "2"
y = x
// According to type declaration of "y" we know, that "e" should be of type "1"
// But we just assigned x to y, and we know that "x" returns "2" in this scenario
// That's not correct
const e = y<string>()
// Same here, according to "y" type this should be "2", but since "y" is now "x",
// this is actually "1"
const f = y<number>()
如果类型不是string
and number
,则类似,它们没有任何共同点,但更复杂。让我们试试{foo: string, bar: number}
forX
和{foo: string}
for Y
。请注意,这里X
实际上可以分配给Y
declare let x: <T>() => (T extends {foo: string, bar: number} ? 1 : 2)
declare let y: <T>() => (T extends {foo: string} ? 1 : 2)
// "a" is of type "2" because {foo: string} doesn't extend {foo: string, bar: number}
const a = x<{foo: string}>()
// "b" is of type "1"
const b = y<{foo: string}>()
y = x
// According to type declaration of "y" this should be of type "1", but we just
// assigned x to y, and "x" returns "1" in this scenario
const c = y<{foo: string}>()
如果您切换类型并尝试{foo: string}
forX
和{foo: string, bar: number}
for Y
,那么调用将再次出现问题y<{foo: string}>()
。你可以看到总是有问题。
更准确地说,如果X
和Y
不相同,总会有某种类型扩展其中一个,而不扩展另一个。如果你尝试使用这种类型,T
你会得到无意义的。实际上,如果您尝试分配y = x
,编译器会给您这样的错误:
Type '<T>() => T extends number ? 1 : 2' is not assignable to type '<T>() => T extends string ? 1 : 2'.
Type 'T extends number ? 1 : 2' is not assignable to type 'T extends string ? 1 : 2'.
Type '1 | 2' is not assignable to type 'T extends string ? 1 : 2'.
Type '1' is not assignable to type 'T extends string ? 1 : 2'.
由于总是有一种类型可以分配给其中一个X
而Y
不是另一个,因此它被迫将返回类型x
视为1 | 2
不可分配给T extends ... ? 1 : 2
,因为T
可以扩展它...
或它不能。
这基本上就是这种Equals
类型的归结,希望它或多或少清楚,它是如何工作的。
升级版:
谈到为什么Equals<{x: 1} & {y: 2}, {x: 1, y: 2}>
是false
tl;dr 据我了解,这是一个实现细节(不确定我是否应该将其称为错误,这可能是有意的)
当然,理论上应该是这样true
。如上所述,当且仅当存在可分配给and之一但不能分配给另一个的类型时,才Equals
返回(理论上)。在上面示例中的这种情况下,如果您这样做并将其粘贴在 (和) 中,您会输入错误的类型。然而,这里并非如此,所有可分配给的东西都是可分配给的,所以理论上应该返回。false
C
C
X
Y
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
部分的地方。以下是相关部分:
// Line 19130
// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
// and Y1 is related to Y2.
// ...
let sourceExtends = (source as ConditionalType).extendsType;
// ...
// Line 19143
if (isTypeIdenticalTo(sourceExtends, (target as ConditionalType).extendsType) && /* ... */) {
// ...
}
评论说这些类型是相关的,如果在其他条件中,U1
和U2
,在我们的例子中X
,和Y
,是相同的,这正是我们想要检查的。在线19143
您可以看到extends
正在比较之后的类型,这会导致isTypeIdenticalTo
函数,而函数又会调用isTypeRelatedTo(source, target, identityRelation)
:
function isTypeRelatedTo(source: Type, target: Type, relation: /* ... */) {
// ...
if (source === target) {
return true;
}
if (relation !== identityRelation) {
// ...
}
else {
if (source.flags !== target.flags) return false;
if (source.flags & TypeFlags.Singleton) return true;
}
// ...
}
您可以看到,它首先检查它们是否是完全相同的类型(就 ts 实现而言,它们不是完全相同的类型),然后比较它们{x: 1} & {y: 2}
的. 如果您查看此处类型的定义,您会发现这是此处定义的类型,您会看一下: intersection有它自己的标志。所以有一个标志,没有,因此它们不相关,因此返回,尽管理论上它不应该。{x: 1, y: 2}
flags
Type
flags
TypeFlags
{x: 1} & {y: 2}
Intersection
{x: 1, y: 2}
Equals
false
推荐阅读
- python - 使用 Pandas DataReader 绘制世界银行数据
- linux - 将文本列表格式化为 csv
- node.js - MEAN 环境设置:卡在 zsh 上:找不到命令,Big Sur
- javascript - 有谁知道我如何动态更改此变量?
- graphql - 如何添加 wpgraphql 自定义 ACF 页面过滤器?
- java - JAXB 编译器将 xs:long 绑定到 Java Long 而不是原始的 long 类
- c# - 在 Windows 窗体中动态更改标签的字体
- python - Django 使用 url 搜索用户
- c++ - linux无法执行文件
- python - 如何从基于 Python 的 Lambda 函数读取和处理 Kinesis Video Stream Signaling Chanel?