【问题标题】:How to Type a Recursive Variadic Tuple如何键入递归可变元组
【发布时间】:2021-12-15 06:18:04
【问题描述】:

我有一个查询元素数组,其中每个元素可以是一个术语或子查询,包含以“AND”或“OR”开头,后跟一个合法查询元素、术语或嵌套子查询的数组,等等开。

例如,这些都应该是合法的输入:

const query1 = "Hello"
const query2 = ["AND", "Hello", "World"]
const query3 = ["OR", ["AND", "Hello", "World"], ["AND", "Hola", "Mundo"]]

Variadric TuplesTS 4.0 中应该允许您键入数组的第一个元素,其余的则使用另一种类型:

type IQuery = ["AND" | "OR", ...Array<string>]
const query = ["AND", "Hello", "World"]  // valid
TS 3.7 中的

Recursive Types 应该允许您定义使用自身的类型:

type IQueryOps = string | Array<IQueryOps>
const query: IQueryOps = ["Hello", "World", ["Hola", "Mundo"]]  // valid

但是当圆形类型展开时,我似乎无法将两者结合起来。在这种情况下,每个查询都以一个运算符开头,然后是一个字符串或另一个像这样的有效查询:

type IOperator = "AND" | "OR"
type IQuery = [IOperator, ...Array<string | IQuery>]

在这种情况下,我得到了错误:

类型别名“IQuery”循环引用自身。(2456)

有没有办法输入这个,即使有变通方法,还是我必须从类型的角度将它解包到我想要支持的所需深度?

Demo in TS Playground

进一步阅读

【问题讨论】:

标签: typescript recursion variadic-tuple-types


【解决方案1】:

查看Recursive Types 的描述,不明确支持可变元组。不知道算不算下面列出的三种类型之一:

此 PR 所做的具体更改是允许类型参数在以下类型的别名类型中进行循环引用:

  • 泛型类和接口类型的实例化(例如Array&lt;Foo&gt;)。
  • 数组类型(例如Foo[])。
  • 元组类型(例如[string, Foo?])。

我通过从您的代码中删除可变参数运算符 ... 来测试这个理论:

type IOperator = "AND" | "OR"
type IQuery = [IOperator, Array<string | IQuery>] // NO ERROR

该更改没有导致错误,但它不支持您想要的语法。

所以我重新编写(并简化)了您的代码,如下所示,它可以正常工作!我的理论是不支持将可变参数运算符与泛型类实例化结合使用,无论是有意还是我不知道的错误。

type IOperator = "AND" | "OR"
type IQuery = string | [IOperator, ...IQuery[]]


// examples from your question
const query1: IQuery = "Hello"
const query2: IQuery = ["AND", "Hello", "World"]
const query3: IQuery = ["OR", ["AND", "Hello", "World"], ["AND", "Hola", "Mundo"]]

// deep nesting
const query4: IQuery = ["OR", ["AND", "Hello", ["AND", "Beautiful", "World"]], ["AND", "Hola", "Mundo"]]


//errors
const query5: IQuery = ["OR", ["NOR", "Hello", ["AND", "Beautiful", "World"]], ["AND", "Hola", "Mundo"]]
const query6: IQuery = ["OR", ["AND", 42, ["AND", "Beautiful", "World"]], ["AND", "Hola", "Mundo"]]


【讨论】:

  • 谢谢 - 很棒的答案!看起来 jcalz 确定了 通用实例化被延迟的差异,所以我们不能递归使用Array&lt;IQuery&gt;,但我们可以使用内置类型IQuery[](正如你所指出的) )
  • @KyleMit 我想你错过了我证明Array&lt;IQuery&gt; 可以递归使用的部分。您可以通过从 Playground 示例中删除可变参数运算符 ... 来自行测试。所以 jcalz 引用的问题没有解释它。正如我所写,它显然是泛型实例化、可变参数运算符和循环性的组合。
【解决方案2】:

我认为这可能是 microsoft/TypeScript#41164 中报告的 TypeScript 设计限制的一个实例。作为mentioned there

允许某些循环[...],但不允许其他循环,例如

type Identity<T> = T;
type T3 = Identity<T3>;

通用实例化是延迟,所以在 TS 分析 [a] 声明时,它无法判断它是在 Record 情况下(将被允许)还是在 Identity 情况下(不允许)。只有在过程的后期,我们才能说这实际上是可以的,但如果它不可以,那么回去开始抱怨就“太晚了”。

如果这是问题所在,那么虽然以下内容应该“没问题”,但编译器似乎无法及早告诉它以允许它:

// type IQuery = string | ["AND" | "OR", ...Array<IQuery>] error

(我稍微更改了您的定义以允许 const query1 = "Hello" 行)。


幸运的是,Array&lt;T&gt; 有一个替代的内置语法 T[],它似乎不会以这种方式延迟:

type IQuery = string | ["AND" | "OR", ...IQuery[]] // okay

这很有效:

const query1: IQuery = "Hello"
const query2: IQuery = ["AND", "Hello", "World"]
const query3: IQuery = ["OR", ["AND", "Hello", "World"], ["AND", "Hola", "Mundo"]]
const query4: IQuery = ["AND", ["OR", ["AND"]]]; 

const badQuery1: IQuery = 3; // error
// Type 'number' is not assignable to type 'IQuery'
const badQuery2: IQuery = ["AND", "Hello", 123]; // error
// Type 'number' is not assignable to type 'IQuery'
const badQuery3: IQuery = 
  ["OR", query1, query2, "then", query3, query4, "if", []]; // error
// Type '[]' is not assignable to type 'IQuery'.

Playground link to code

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2021-09-03
  • 1970-01-01
  • 2014-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-27
  • 1970-01-01
相关资源
最近更新 更多