【问题标题】:Typescript type inference with union of tuple types具有元组类型联合的打字稿类型推断
【发布时间】:2022-01-05 02:01:33
【问题描述】:

给定以下类型

type Tool = 'network_query' | 'cluster'
type Network = 'one' | 'two' | 'three'

class QueryOneParams {...}
class QueryTwoParams {...}
class QueryThreeParams {...}
class ClusterParams {...}

我正在尝试定义Tool 和Network 组合之间的映射,以便我可以编写如下内容:

const queryParamsLookup = {
    'one': QueryOneParams,
    'two': QueryTwoParams,
    'three': QueryThreeParams
}

type Job<Tool, Network> = {
    id: string,
    params: JobParams<Tool, Network>
}

在哪里

  • JobParams&lt;'network_query', 'one'&gt; 解析为 QueryOneParams
  • JobParams&lt;'network_query', 'two'&gt; 解析为 QueryTwoParams
  • JobParams&lt;'network_query', 'three'&gt; 解析为 QueryThreeParams
  • JobParams&lt;'cluster'&gt; 解析为 ClusterParams
  • JobParams&lt;'cluster', 'one'&gt;、JobParams&lt;'cluster', 'two'&gt; 和 JobParams&lt;'cluster', 'three'&gt; 无效

这需要我以某种方式定义第二个泛型参数'one' | 'two' | 'three' 仅在第一个参数是'network_query' 时才使用和必需。 Afaik,Typescript 不支持基于另一个参数的类型可选定义的泛型参数。

正确吗?我很想在这里犯错:)

作为替代方案,我定义了一个辅助类型,如下所示:

type NetworkQueryJobType = {
    [N in keyof typeof queryParamsLookup]: ['network_query', N]
}[keyof typeof queryParamsLookup]
// ['network_query', 'one'] | ['network_query', 'two'] | ['network_query', 'three']

type JobType = NetworkQueryJobType | ['cluster']
// ['network_query', 'one'] | ['network_query', 'two'] | ['network_query', 'three'] | ['cluster']

并将Job的定义改为

type Job<JobType> = {
    id: string,
    params: JobParams<JobType>
}

使用这种方法,我无法让类型推断在映射器类型JobParams 中正常工作:

type JobParams<T extends JobType> = T extends ['network_query', infer N] ?
typeof queryParamsLookup[N] // Error: N is not compatible with 'one' | 'two' | 'three'
: ClusterParams

我可以解决类型推断问题:

type JobParams<T extends JobType> = T extends ['network_query', infer N] ?
N extends Network ?
    typeof queryParamsLookup[N] // No error
    : ClusterParams
: ClusterParams

然而,所有这些仍然让我在键入时自动完成性能不佳,例如:

const params: JobParams<['

VSCode 不会建议'cluster' | 'network_query'

总而言之,我觉得在这里打一场失败的战斗。我做错了什么?

Playground Link

【问题讨论】:

  • 你不是在queryParamsLookup中使用QueryOneParams作为表达式吗?
  • 是的,你完全正确!我的意图是抽象出我的一些实现细节。实际上,QueryOneParams 和朋友是班级。我会在问题中更改它。谢谢你抓住这个:)
  • 我已经为你添加了一个游乐场链接(太长了,无法放入评论)。您的 Job 类型出错。请修复它并更新问题中的链接。

标签: typescript typescript-typings typescript-generics


【解决方案1】:

您可以使用通用参数默认值和中性类型(例如null)来匹配提供的行为。

我对你的代码做了一些假设,如果我解释不正确,请纠正我。

首先,queryParamsLookup 是一个类型QueryParamsLookup:

type QueryParamsLookup = {
    'one': QueryOneParams,
    'two': QueryTwoParams,
    'three': QueryThreeParams
}

按照问题中定义的方式定义对象会将类型解析为提供的类构造函数的类型,这可能不是预期的,因为这种情况非常罕见。

JobParams 类型可以定义为:

type JobParams<T extends Tool, N extends T extends "network_query" ? Network : null = T extends "network_query" ? Network : null> =
    N extends Network ? QueryParamsLookup[N] : ClusterParams

满足以下要求:

// valid cases
const one: JobParams<"network_query", "one"> = new QueryOneParams()
const two: JobParams<"network_query", "two"> = new QueryTwoParams()
const three: JobParams<"network_query", "three"> = new QueryThreeParams()
const cluster: JobParams<"cluster"> = new ClusterParams()

// invalid cases
const inv1: JobParams<"cluster", "one"> = new QueryOneParams()
const inv2: JobParams<"cluster", "two"> = new QueryTwoParams()
const inv3: JobParams<"cluster", "three"> = new QueryThreeParams()

JobParams 的此定义在提供联合作为其第一个参数时是悲观的,这意味着它将把联合视为不是 network_query。这种行为可以通过翻转三元运算符来反转。

带有解决方案的playground 的链接。

虽然这也完全可以用元组泛型来实现,但在我看来它过于夸张而且不是很直观。

【讨论】:

  • 看起来不错。我有两个未解决的问题: - 有没有办法将条件封装在JobParams 中,这样我就可以在我的问题中的Job 类型中使用它们,而不必输入两次? - JobParams&lt;'network_query'&gt; 现在也是一个有效的定义(映射到QueryOneParams | QueryTwoParams | QueryThreeParams),这是出乎意料的,但实际上非常方便。但是,有没有办法让JobParams&lt;'network_query'&gt; 引发错误,因为缺少第二个参数?
  • 问题 #1:我想类型别名可能会有所帮助,但这可能会使类型系统超出其限制。我找不到在当前版本中实现该功能的方法。
  • 问题 #2:是的,肯定有办法做到这一点,当只有 "network_query" 是第一个参数时,回退到 never,如下所示:type JobParams&lt;T extends Tool, N extends T extends "network_query" ? Network : null = T extends "network_query" ? never : null&gt; = N extends Network ? QueryParamsLookup[N] : ClusterParams
  • 问题 #2 playground,不幸的是,由于存在评论字符限制,因此被精简了
  • 非常好。感谢您的所有努力和非常好的解释:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-08-12
  • 2018-07-30
  • 1970-01-01
  • 1970-01-01
  • 2018-06-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多