TypeScript - 泛型中的结构比较
我做了以下一个小实验:
type Equal<X,Y> =
(<T>()=> T extends X ? 1 : 2) extends (<T>()=> T extends Y ? 1 : 2)
? true
: false;
type Res = Equal<any, 1>; // type Res = false
我以为 Equal<X,Y> 会变成:
type Equal<X,Y> =
(<T>()=> T extends any ? 1 : 2) extends (<T>()=> T extends 1 ? 1 : 2)
第一个函数:
<T>() => T extends any ? 1 : 2→ 总是返回1(因为所有类型都继承自any)。第二个函数:
<T>() => T extends 1 ? 1 : 2→ 仅当T=1时返回1,否则返回2。
因此,我认为这意味着第一个函数是 ()=>1,第二个函数是 ()=>1|2
但以下结果让我感到困惑:
type FuncTest<F1, F2> = F1 extends F2 ? true : false;
type Res = FuncTest<()=>1, ()=>1|2> // type Res = true
作为类型参数传入的函数的返回类型 1 与 1|2 是兼容的,所以上面的结果是可以理解的。然而,我不明白为什么第一个结果是 false。
我尝试过的:
我向 Chat GPT 提了一个相关问题,然后我发现它与 结构类型系统 有关。我是这样理解的:
在 (<T>()=> T extends any ? 1 : 2) extends (<T>()=> T extends 1 ? 1 : 2) 的情况下,结果与 T 的类型有关,因此 TypeScript 会比较它们的结构(它们的行为是否完全相同)。因此,结果是 false,因为它们的行为不完全相同。
在 FuncTest<()=>1,()=>1|2> 的情况下,作为类型参数传递的函数总是返回 1 和 1|2。因此 TypeScript 只检查 类型兼容性。
我理解正确吗?
答案#1
不,Equal 工具类型,如在 microsoft/TypeScript#27024 的评论中介绍的,是试图实现一个类型级的 恒等 或 严格相等 运算符。这 不是 结构化类型。两个仅在结构上等价的类型,可能在此测试中不会显示为“相等”:
type A = { x: string, y: string }
type B = { x: string } & { y: string }
type Res = Equal<A, B>; // false
这里 A 和 B 拥有完全相同的结构,但它们不被认为是“相等”的。
使用像 <T>()=>T extends X?1:2 这样的分布式条件类型的奇怪通用函数的全部意义在于尝试让 TypeScript 根本不去实际计算 X。通用条件类型往往是延迟的;这个类型是一个函数,TypeScript 在调用函数之前并不知道 T 会是什么,只有到调用函数时才可能确定 T extends X 是否为真。由于这只是一个类型并且没有人真正调用该函数,TypeScript 永远不会完全计算该类型。它只是将其保持为一个不透明的“黑箱”,<T>()=>T extends X?1:2。
在比较两个黑箱 <T>()=>T extends X?1:2 和 <T>()=>T extends Y?1:2 时,TypeScript 通常完全无法将它们视为相关的,除非它认为它们是相同的 黑箱。唯一可能发生这种情况的方式是 X 和 Y 的表示方式完全相同。
因此,Equal 工具类型本质上是在探查 TypeScript 对 X 和 Y 类型的内部表示,以查看它们是否相同。这样的探查虽然可能提供信息,但并不是你通常应该进行的操作。它依赖于实现,这意味着在未来某个版本的 TypeScript 中,Equal<A, B> 可能会根据 TypeScript 类型检查器的某些细节从 false 变为 true 或相反。
关于结构化类型的 ChatGPT 回答是一个烟雾弹。截至我撰写本文时,生成式 AI 对像这样的问题在 Stack Overflow 上并不是真正有用的资源。AI 生成的答案很可能从根本上是错误的。区分好的 AI 生成答案和坏的唯一方法是已经在相关领域具有专业知识,或者能够直接测试该答案。如果你在询问自己不熟悉且无法自己测试的内容,那么你可能应该避免向 AI 提问。
代码的 Playground 链接
