typescript - Rekursivt typnamn


Jag vill skapa ett objekt som kan ha antingen primitiva typer eller objekt som liknar sig själv som egenskaper. Jag menar något sådant skulle vara fel:

const obj: DesiredType = {
  correctProp1: 'string',
  correctProp2: 123,
  correctProp3: true,
  wrongProp4: [1, 2, 3],
  prop5: {
    wrongProp1: () => {},         // Is neither an object nor a primitive
    wrongProp2: [1, 2, 3],        // Same
    correctProp3: 'other string'
  }
}

Jag vill inte bara använda Record<string, any>, eftersom allt kan läggas där på det sättet (funktion, array, etc.). Att använda något som följande:

Record<string, string | number | boolean | Record<string, any>>

skulle också vara dåligt, eftersom vad som helst kan dyka upp på andra nivån

Jag har försökt detta:

type PrimitiveOrObject = string | number | boolean | Record<string, PrimitiveOrObject>; // ts(2456) type 

DesiredType = Record<string, PrimitiveOrObject>;

Men jag får fel ts(2456): Typaliaset "PrimitiveOrObject" refererar cirkulärt till sig självt.

Finns det alltså ett sätt att skapa en typalias för objekt som innehåller antingen primitiva typer eller objekt som liknar sig själva?

Svar #1

Du kan inte använda en typalias generiskt i samma definition som skapar den. Men du kan använda den i objekt typliteraler, så om vi lägger till lite indirektion kan vi få den rekursion du vill ha.

type PrimitiveOrObject =
  string | number | boolean | { [key: string]: PrimitiveOrObject };

Syntaxen { [key: string]: PrimitiveOrObject } kallas för ett indexsignatur. För nyckeltypen string är det ekvivalent med Record (se denna fråga för detaljer), men det har fördelen att vara inne i ett objektliteral och därmed inte omfattas av rekursionsbegränsningen som du stött på.

Svar #2

Jag vill lägga till Silvios svar att det inte handlar om indirektion, objekttyp eller indexsignatur.

Till exempel gör en ytterligare omskrivning att problemet återkommer:

type PrimitiveOrObject = string | number | boolean | F<PrimitiveOrObject>;
//   ~~~~~~~~~~~~~~~~~ PrimitiveOrObject circularly references itself

type F<T> = { [k: string]: T }

Och andra typer av sammansättningar har samma beteende som objektet med en indexsignatur

type PrimitiveOrObject = string | number | boolean | PrimitiveOrObject[];
//                                  works just fine  -------------------

Jag tycker att en bra mental modell att ha är att en typaliasdeklaration bara kan nämna sig själv i en typkomposition med en inbyggd typkonstruktor. Alla hjälpetyper får sannolikt TypeScript att ge upp, förmodligen eftersom det inte finns något generellt sätt att veta när man ska sluta återkomma medan man kontrollerar typdefinitionen. Inbyggda typkonstruktorer är kända specialfall som kan ha inbakad beteende när det gäller rekursion.

Relaterade artiklar