如何从键而不是值推断类型参数?

问题描述 投票:0回答:2

我有一个表示有向图结构的类,它是通用的,具有一个类型参数

K extends string
作为节点名称。通过传递像
{a: ['b'], b: []}
这样的对象来构造图,在这个最小示例中,该对象表示两个节点 ab,其中一条边 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
是对象字面量中的多余属性。

// 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

游乐场链接


我尝试的一个解决方案是添加另一个类型参数

T extends Record<K, K[]>
并声明
constructor(readonly adjacencyList: T) {}
。然后多余的属性错误消失,但现在
K
只能推断为
string

此外,类型

Digraph<K, T>
太具体了 - 具有相同节点的两个有向图应该可以彼此分配,即使它们具有不同的边,而且我宁愿不必编写
Digraph<K, Record<K, K[]>>
Digraph<K, any>
来解决问题这。我正在寻找一种解决方案,如果可能的话,不会添加额外的类型参数或更改
K
的内容。

typescript generics type-inference
2个回答
2
投票

所以你的问题是,

K
类型中有多个
Record<K, K[]>
的候选推理站点,并且编译器的推理算法优先考虑了错误的推理站点。 您希望能够告诉编译器它不应该使用第二个
K
(在属性值位置的数组元素中)进行推理,而应该只使用第一个
K
(在属性关键位置)用于此目的。 它应该只注意推断
K
之后的第二个站点,并且只检查推断的类型是否有效。


TypeScript 5.4 引入了

NoInfer<T>
实用程序类型来支持非推理类型参数用法。 类型
NoInfer<T>
最终仅计算为
T
,但仅在发生 之后 类型推断。 所以你可以这样写:

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

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

一切都会正常进行:

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

Playground 代码链接


2
投票

您似乎正在寻找一种

NoInfer<T>
类型,该类型声明
T
只能用于类型检查,而不应用于推理,如此 TypeScript 问题中所述。它尚未添加到 TypeScript,但来自 jcalz 的定义适用于您的代码:

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

如果您将记录重写为

Record<K, NoInfer<K>[]>
,则该类将变为

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

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

并且

digraph
示例输入正确:

// inferred type: Digraph<"a" | "b">
let digraph = new Digraph({
    a: ['b'],
    b: [],
});

TypeScript 游乐场

请记住,它仍然是一种黑客行为,未来的改进可能使类型检查器能够推断

NoInfer<T> = T
并阻止它阻止推断。

© www.soinside.com 2019 - 2024. All rights reserved.