我的函数有一个通用参数T
和另一个基于传递的T
.
例子:
const foo = {
a: 'hello',
b: 'world',
} as const
function bar<T extends object, K extends keyof T>(obj: T, key1: K, key2: K) {};
bar(foo, 'a', 'b') // No error, even though key1 should narrow K to 'a'. K stays 'a'|'b'
我的目标最终是传递一个对象,根据其类型约束其他参数,并在传递它们时推断它们的值以进一步约束其他参数:
- 传递的参数推断
T
和约束K
- 传递第二个参数推断
K
和约束V
- ETC。
当泛型类型参数存在多个推理候选时,需要进行权衡 。如果你有一个类似的函数
有人打电话来
应该发生什么?在一种极端情况下,编译器可能会拒绝任何类型
A
不同的调用B
。在另一个极端,它可以通过合成联合类型A | B
来接受任何东西。这两种行为和介于两者之间的行为都有用例,并且不同的人在不同的时间想要不同的东西。因此,目前 TypeScript 仅使用启发式规则,这些规则被发现在大范围的现实场景中运行良好,但有时会让人失望。
启发法主要是联合类型不会被合成,除非多个候选者是同一原始类型的一部分。因此,如果您使用
"a"
and (字符串和数字)代替and (两个字符串),您会得到预期的错误:"b"
"a"
3
但是,当两种类型都是文字类型并且会扩展为相同的原始类型时(例如
"a"
和"b"
都是 的子类型)string
,TypeScript会合成联合类型以使其成功,如您所见。这不是您想要的,但同样,这些启发法在许多其他情况下也很有效。
那么,有没有办法告诉编译器你想要别的东西呢?在这种情况下,您想说“
key2
只能根据从 推断出的类型进行检查。它不应该用于推断自身”。也就是说,应该是“非推理类型参数用法”,例如microsoft/TypeScript#14829中请求的假设实用程序类型:K
key1
K
key2
NoInfer<T>
这是一个悬而未决的问题,因此目前还没有
NoInfer<T>
. 幸运的是,其中讨论了各种方法,您可以使用它们自己定义它,这些方法适用于某些用例(但似乎没有任何方法在任何地方都适用)。这里描述的一种方法是:NoInfer<T>
与 相比,它分配了较低的推理优先级T
,因此编译器将倾向于从 推断T
而不是从推断NoInfer<T>
。如果你这样写,你就会得到你想要的行为。另一种方法是仅声明受第一个类型参数约束的另一个类型参数。它还会阻止推理:
无论哪种方式都应该有效,至少对于问题中的示例代码来说是这样。
Playground 代码链接