typescript - Hur man typer en rekursiv variadisk tuppel
Jag har en matris av frågelement, där varje element kan vara en term eller en underfråga som börjar med antingen "AND" eller "OR", och följs av en matris av giltiga frågelement, termer eller nästlade underfrågor, och så vidare.
Till exempel bör alla dessa vara giltiga indata:
const query1 = "Hello"
const query2 = ["AND", "Hello", "World"]
const query3 = ["OR", ["AND", "Hello", "World"], ["AND", "Hola", "Mundo"]]
Variadric-tupler i TS 4.0 bör tillåta dig att skriva typen för det första elementet i en array och en annan typ för resten:
type IQuery = ["AND" | "OR", ...Array<string>]
const query = ["AND", "Hello", "World"] // valid
Rekursiva typer i TS 3.7 bör tillåta dig att definiera en typ som använder sig själv:
type IQueryOps = string | Array<IQueryOps>
const query: IQueryOps = ["Hello", "World", ["Hola", "Mundo"]] // valid
Men jag verkar inte kunna kombinera de två när den cirkulära typen är spridd. I detta fall börjar varje fråga med en operator, och följs sedan av antingen en sträng eller en annan giltig fråga som denna:
type IOperator = "AND" | "OR"
type IQuery = [IOperator, ...Array<string | IQuery>]
I det här fallet får jag felet:
Typalias 'IQuery' refererar cirkulärt till sig själv.(2456)
Finns det något sätt att skriva detta, även med kringlösningar, eller måste jag packa upp det till den önskade nivå av djup som jag vill stödja från ett typ-perspektiv?
Demo i TS Playground
Vidare läsning
- Definiera en typ för en array där det första elementet är mer specifikt än resten
- Hur skapar man en cirkulärt refererad typ i TypeScript?
Svar #1
Jag tror att detta kan vara ett exempel på den TypeScript-designbegränsning som rapporterats i microsoft/TypeScript#41164. Som nämnts där,
Vissa cirkulariteter är tillåtna [...] men andra cirkulariteter är inte, t.ex.
type Identity<T> = T; type T3 = Identity<T3>;Generisk instansiering är fördröjd, så vid den tidpunkt då TS analyserar [en] deklaration kan det inte avgöra om det är i
Record-fallet (skulle vara tillåtet) ellerIdentity-fallet (skulle inte vara tillåtet). Det är först senare i processen som vi skulle kunna avgöra att det faktiskt är okej, men om det inte var okej är det "för sent" att gå tillbaka och börja klaga på det.
Om det är problemet, då, medan följande borde vara "okej", verkar det som att kompilatorn inte kan avgöra tidigt nog för att tillåta det:
// type IQuery = string | ["AND" | "OR", ...Array<IQuery>] error
(Jag ändrade din definition något för att tillåta raden const query1="Hello").
Lyckligtvis har Array<T> en alternativ inbyggd syntax T[] som inte verkar vara uppskjuten på det sättet:
type IQuery = string | ["AND" | "OR", ...IQuery[]] // okay
Och detta fungerar:
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'.
Lekplatslänk till kod
Svar #2
När man tittar på beskrivningen av rekursiva typer stöds inte variadiska tupler uttryckligen. Jag vet inte om det räknas som en av de tre typer som listas nedan:
Den specifika ändringen som gjorts av denna PR är att typargument tillåts göra cirkulära referenser i aliasade typer av följande slag:
- Instanser av generiska klass- och gränssnittstyper (till exempel
Array<Foo>).- Arraytyper (till exempel
Foo[]).- Tuple-typer (till exempel
[string, Foo?]).
Jag testade denna teori genom att ta bort det variadiska operatorn ... från din kod:
type IOperator = "AND" | "OR"
type IQuery = [IOperator, Array<string | IQuery>] // NO ERROR
Den ändringen resulterade i inget fel, men det stöder inte den syntax du vill ha.
Så jag omformulerade (och förenklade) din kod som nedan, och den fungerar! Min teori är att användningen av den variadiska operatorn i kombination med en generisk klassinstansiering inte stöds, om det är avsiktligt eller ett fel vet jag inte.
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"]]
