[英]How to infer type parameter from keys instead of values?
I have a class representing a directed graph structure, which is generic with one type parameter K extends string
for the node names.我有一个 class 代表一个有向图结构,它是通用的,具有一个类型参数
K extends string
。 A graph is constructed by passing an object like {a: ['b'], b: []}
which in this minimal example represents two nodes a and b , with one edge a → b .一个图是通过传递一个 object 来构造的,例如
{a: ['b'], b: []}
,在这个最小的例子中代表两个节点a和b ,一条边a → b 。
class Digraph<K extends string> {
constructor(readonly adjacencyList: Record<K, K[]>) {}
getNeighbours(k: K): K[] {
return this.adjacencyList[k];
}
}
However, declared like this, the type parameter K
is inferred from the contents of the arrays, instead of from object's property names.然而,像这样声明,类型参数
K
是从 arrays 的内容中推断出来的,而不是从对象的属性名称中推断出来的。 This means K
becomes 'b'
instead of 'a' | 'b'
这意味着
K
变为'b'
而不是'a' | 'b'
'a' | 'b'
, and so Typescript gives an error because it thinks a
is an excess property in an object literal. 'a' | 'b'
,因此 Typescript 给出错误,因为它认为a
是 object 文字中的多余属性。
// inferred as Digraph<'b'> instead of Digraph<'a' | 'b'>
// error: Argument of type '{ a: string[]; b: never[]; }' is not assignable to parameter of type 'Record<"b", "b"[]>'.
let digraph = new Digraph({
a: ['b'],
b: [],
});
Is there a way to have K
inferred directly from the property names, instead of their values?有没有办法让
K
直接从属性名称而不是它们的值中推断出来?
One solution I tried is to add another type parameter T extends Record<K, K[]>
and declare constructor(readonly adjacencyList: T) {}
.我尝试的一个解决方案是添加另一个类型参数
T extends Record<K, K[]>
并声明constructor(readonly adjacencyList: T) {}
。 Then the excess property error goes away, but now K
is only inferred as string
.然后多余的属性错误消失了,但现在
K
只被推断为string
。
Also, the type Digraph<K, T>
is too specific - two digraphs with the same nodes should be assignable to each other even when they have different edges, and I'd rather not have to write Digraph<K, Record<K, K[]>>
or Digraph<K, any>
to get around this.此外,类型
Digraph<K, T>
过于具体 - 具有相同节点的两个有向图应该可以相互分配,即使它们具有不同的边,我宁愿不必写Digraph<K, Record<K, K[]>>
或Digraph<K, any>
来解决这个问题。 I'm looking for a solution which doesn't add an extra type parameter or change what K
would be, if possible.如果可能的话,我正在寻找一种不添加额外类型参数或更改
K
的解决方案。
It seems you're looking for a NoInfer<T>
type that states that T
should only be used for type checking but not for inference, as discussed in this TypeScript issue .似乎您正在寻找一个
NoInfer<T>
类型,该类型声明T
应该只用于类型检查而不是用于推理,正如TypeScript 问题中所讨论的那样。 It hasn't been added to TypeScript, but this definition from jcalz works for your code:它尚未添加到 TypeScript,但 jcalz 的此定义适用于您的代码:
type NoInfer<T> = [T][T extends any ? 0 : never];
If you rewrite the record as Record<K, NoInfer<K>[]>
the class becomes如果将记录重写为
Record<K, NoInfer<K>[]>
则 class 变为
class Digraph<K extends string> {
constructor(readonly adjacencyList: Record<K, NoInfer<K>[]>) {}
getNeighbours(k: K): K[] {
return this.adjacencyList[k];
}
}
and the digraph
example gets typed correctly:并且
digraph
示例被正确输入:
// inferred type: Digraph<"a" | "b">
let digraph = new Digraph({
a: ['b'],
b: [],
});
TypeScript playground TypeScript操场
Keep in mind it's still kind of a hack though, and future improvements might enable the type checker to infer NoInfer<T> = T
and stop it from blocking inference.请记住,它仍然是一种 hack,未来的改进可能使类型检查器能够推断
NoInfer<T> = T
并阻止它阻止推断。
So your problem is that there are multiple inference site candidates for K
in the type Record<K, K[]>
, and that the compiler's inference algorithm is giving priority to the wrong one.所以你的问题是在
Record<K, K[]>
类型中有多个K
的推理站点候选者,并且编译器的推理算法优先考虑错误的。 You would like to be able to tell the compiler that it should not use the second K
(in the elements of the array in the property value position) for inference, and that it should only use the first K
(in the property key position) for this purpose.您希望能够告诉编译器它不应该使用第二个
K
(在属性值位置的数组元素中)进行推理,并且它应该只使用第一个K
(在属性键位置)以此目的。 It should only pay attention to that second site after K
is inferred, and only to check that the inferred type works.它应该只注意推断
K
之后的第二个站点,并且只检查推断的类型是否有效。
There is an open issue at microsoft/TypeScript#14829 asking for such non-inferential type parameter usages . microsoft/TypeScript#14829有一个未解决的问题,要求使用此类非推理类型参数用法。 The idea is that there should be some type function called
NoInfer<T>
where the type NoInfer<T>
eventually evaluates just to T
, but only after type inference has occurred.这个想法是应该有一些类型 function 称为
NoInfer<T>
类型NoInfer<T>
最终仅计算为T
,但仅在发生类型推断之后。 Then you would write this:然后你会这样写:
class Digraph<K extends string> {
constructor(readonly adjacencyList: Record<K, NoInfer<K>[]>) { }
getNeighbours(k: K): K[] {
return this.adjacencyList[k];
}
}
and everything should just work.一切都应该正常工作。
While no official version of NoInfer
exists, there are some user-made implementations mentioned inside microsoft/TypeScript#14829 that work for some use cases.虽然没有官方版本的
NoInfer
,但在 microsoft/TypeScript#14829 中提到了一些适用于某些用例的用户自定义实现。 The one I tend to recommend is:我倾向于推荐的是:
type NoInfer<T> = [T][T extends any ? 0 : never];
Evaluation of the conditional type T extends any? 0: never
条件类型
T extends any? 0: never
T extends any? 0: never
is (currently for TS4.2) deferred until T
is a specific type. T extends any? 0: never
(目前为 TS4.2)推迟到T
为特定类型。 So while NoInfer<T>
will eventually evaluate to T
, the compiler cannot see this.因此,虽然
NoInfer<T>
最终将评估为T
,但编译器看不到这一点。
Hopefully microsoft/TypeScript#14829 will eventually get an official implementation, so that the workarounds mentioned in there can be abandoned in favor of it.希望 microsoft/TypeScript#14829 最终会得到官方实现,以便可以放弃其中提到的变通方法以支持它。 Or at least the existing workarounds would get promoted to supported features.
或者至少现有的解决方法将被提升为受支持的功能。 (The
type NoInfer<T> = T & {}
version is about as supported as it can be , but unfortunately that will not work for your use case.) (
type NoInfer<T> = T & {}
版本已得到尽可能多的支持,但不幸的是,这不适用于您的用例。)
Anyway, you can check that this definition of NoInfer<T>
will behave as you want in your example code:无论如何,您可以检查
NoInfer<T>
的这个定义在您的示例代码中的行为是否符合您的要求:
let digraph = new Digraph({
a: ['b'],
b: [],
}); // okay, Digraph<"a" | "b">
let badDigraph = new Digraph({
a: ['c'], // error, "c" is not assignable to "a" | "b"
b: []
})
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.