如何从键而不是值推断类型参数? [英] How to infer type parameter from keys instead of values?

查看:24
本文介绍了如何从键而不是值推断类型参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个表示有向图结构的类,它是通用的,具有一个类型参数 K extends string 用于节点名称.图是通过传递一个像 {a: ['b'], b: []} 这样的对象来构建的,在这个最小的例子中它代表了两个节点 ab,一个边ab.

I have a class representing a directed graph structure, which is generic with one type parameter K extends string for the node names. 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.

class Digraph<K extends string> {
    constructor(readonly adjacencyList: Record<K, K[]>) {}

    getNeighbours(k: K): K[] {
        return this.adjacencyList[k];
    }
}

然而,像这样声明,类型参数 K 是从数组的内容中推断出来的,而不是从对象的属性名称中推断出来的.这意味着 K 变成了 'b' 而不是 'a' |'b',因此 Typescript 会报错,因为它认为 a 是对象字面量中的多余属性.

However, declared like this, the type parameter K is inferred from the contents of the arrays, instead of from object's property names. This means K becomes 'b' instead of 'a' | 'b', and so Typescript gives an error because it thinks a is an excess property in an object literal.

// 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: [],
});

有没有办法让 K 直接从属性名称而不是它们的值推断出来?

Is there a way to have K inferred directly from the property names, instead of their values?

游乐场链接

我尝试过的一个解决方案是添加另一个类型参数 T extends Record 并声明 constructor(readonly adjacencyList: T) {}.然后多余的属性错误消失了,但现在 K 仅被推断为 string.

One solution I tried is to add another type parameter T extends Record<K, K[]> and declare constructor(readonly adjacencyList: T) {}. Then the excess property error goes away, but now K is only inferred as string.

此外,类型 Digraph 过于具体 - 具有相同节点的两个有向图应该可以相互分配,即使它们具有不同的边,我宁愿没有编写 Digraph>Digraph 来解决这个问题.如果可能,我正在寻找一种不添加额外类型参数或更改 K 的解决方案.

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. I'm looking for a solution which doesn't add an extra type parameter or change what K would be, if possible.

推荐答案

所以你的问题是 K 在类型 Record,并且编译器的推理算法优先考虑错误的算法.您希望能够告诉编译器它不应该使用第二个 K(在属性值位置的数组元素中)进行推理,并且它应该只使用第一个 K(在属性键位置)用于此目的.它应该只关注 K 推断后的第二个站点,并且只检查推断的类型是否有效.

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. 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. It should only pay attention to that second site after K is inferred, and only to check that the inferred type works.

microsoft/TypeScript#14829 有一个未解决的问题,要求这样 非推理类型参数用法.这个想法是应该有一些名为 NoInfer 的类型函数,其中类型 NoInfer 最终只计算为 T,但是仅在 类型推断发生之后.然后你会这样写:

There is an open issue at microsoft/TypeScript#14829 asking for such non-inferential type parameter usages. 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. 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];
    }
}

一切都应该正常工作.

虽然不存在 NoInfer官方版本,但在 microsoft/TypeScript#14829 中提到了一些适用于某些用例的用户实现.我倾向于推荐的是:

While no official version of NoInfer exists, there are some user-made implementations mentioned inside microsoft/TypeScript#14829 that work for some use cases. The one I tend to recommend is:

type NoInfer<T> = [T][T extends any ? 0 : never];

条件类型的评估 T extends any ?0 : never 是(当前用于 TS4.2)延迟,直到 T 是特定类型.因此,虽然 NoInfer 最终会评估为 T,但编译器看不到这一点.

Evaluation of the conditional type T extends any ? 0 : never is (currently for TS4.2) deferred until T is a specific type. So while NoInfer<T> will eventually evaluate to T, the compiler cannot see this.

希望 microsoft/TypeScript#14829 最终会得到官方实现,以便可以放弃其中提到的变通方法以支持它.或者至少现有的解决方法会被提升为支持的功能.(type NoInfer = 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. 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.)

无论如何,您可以检查 NoInfer<T> 的此定义在示例代码中的行为是否符合您的要求:

Anyway, you can check that this definition of NoInfer<T> will behave as you want in your example code:

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: []
})

游乐场连结代码

这篇关于如何从键而不是值推断类型参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆