TypeScript中交叉类型键的含义

3

我希望编写一个类型安全的工具函数,用于比较 Typescript 4.0 中给定两个对象的属性。我的初始尝试如下:

export function propsAreEqual<O extends object, T extends O, S extends O>(first: T, second: S, props: (keyof O)[]) {
    return props.every(prop => first[prop] === second[prop])
}

然而,使用这种方法我遇到了编译错误TS2367,错误信息如下: This condition will always return 'false' since the types 'T[keyof O]' and 'S[keyof O]' have no overlap. 对我来说,该错误似乎不符合直觉。如果 TS 都扩展了类型为 O 的对象,那么它们两个不都必须包含类型为 O 的所有键吗?如果有人能澄清我在这里缺少什么,并且指出我尝试实现的更稳妥的方法,我将不胜感激。
2个回答

2

您收到的错误不是错误的,扩展 O 只意味着扩展 object,这意味着您可以有 T = { a: number }O = { a: string }ex)。键相同,但 T[keyof O]O[keyof O] 之间没有重叠。可能会有重叠,但在处理 TS 时,希望能够证明该函数对类型参数的任何有效实例化都是正确的,而正如我们在此处看到的那样,存在无效函数的情况。
你可以有几种方法来定义这个函数,你可以使用单个类型参数来表示第一个对象,并将第二个对象定义为 Pick 或传入的属性。
function propsAreEqual<T extends object, K extends keyof T>(first: T, second: Pick<T, K>, props: K[]) {
    return props.every(prop => first[prop] === second[prop])
}

操场链接

这个版本将确保T[K]对于两个参数是相同的类型。这个版本的缺点是,如果您尝试将对象字面量作为第二个参数传递进去,过多的属性检查会启动。另一个缺点是,智能提示将在第三个参数中建议所有T的属性,如果属性不常见,则会在第二个参数中出现错误。
个人而言,我更喜欢在函数中牺牲完全类型检查以解决上述两个问题的选项。这将是以下版本:
function propsAreEqual<T extends object,  S extends Pick<T, K>,  K extends keyof T & keyof S>(first: T, second: S, props: K[]) {
    return props.every(prop => first[prop] === (second as Pick<T, K>)[prop])
}


游乐场链接


很高兴看到我基本上正确地找到了错误的原因。:-) 很好的解决方案!我不认为我意识到类型参数可以像那样具有循环引用。哇,太酷了。 - T.J. Crowder
确实,@t-j-crowder,你说得对。可惜看到你的回答被删除了 :/ 而且@titian-cernicova-dragomir的最终解决方案似乎正好做到了我想要的,即从对象类型推断数组类型。"扩展O只是表示扩展object" 哦,好的,这样更清楚了。我再一次对TS如何推断类型有错误的假设... - arslancharyev31
@arslancharyev31 - 非常友善。:-) 但是考虑到Titian的回答已经涵盖了要点,我认为我的回答并没有增加任何价值。如果您愿意,我可以将其恢复,但上面的内容已经很好地涵盖了要点。祝编码愉快! - T.J. Crowder

1
当您推断类型参数时,Typescript 希望仅从一个参数中推断出它;因此,签名 propsAreEqual<T>(a: T, b: T, props: (keyof T)[]) 不起作用,因为 Typescript 从第一个参数的类型推断出 T,然后在发现第二个参数不能分配给该类型时不会尝试放松它。
解决方案:使用一个泛型类型,它将从 props 中推断而不是从 ab 中推断。
function propsAreEqual<K extends PropertyKey>(a: Record<K, unknown>, b: Record<K, unknown>, props: K[]) {
    return props.every(prop => a[prop] === b[prop]);
}

游乐场链接


那也是一个非常有趣的解决方案,但它似乎在功能上等同于 @t-j-crowder 提供的示例,尽管更简洁地编写。我提出的问题是是否可能让TS从提供的对象中推断 props 参数的类型,因为原则上应该是可能的。 - arslancharyev31
它与T.J.克劳德的答案中的任何一个示例都不等价。请注意,他的第一个示例不符合要求,而他的第二个示例具有三个类型参数和类型断言。 - kaya3
我的意思是,它们两个基本上都使用给定的数组进行类型推断,然后验证提供的对象,不是吗?我只是想知道是否有可能反过来做,即让TS推断出2种类型的公共属性,然后用它来验证给定的数组。这在功能上是不同的,因为它还可以启用代码完成。 - arslancharyev31
我明白你所说的代码补全问题,但我认为问题在于Typescript仅从一个参数中推断出类型参数,而不是两个参数的公共属性。如果你对这两个参数有两个不同的类型参数(以允许它们不同),那么你将会得到(逻辑上不正确的)错误,即条件始终为false。我不能确定是否有解决方法,但直觉告诉我可能性不大。 - kaya3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接