Website-Suche

TypeScript - Fehler beim Verwenden eines rekursiven Typs in einer Funktion


Ich habe einen rekursiven Typ, der die Indizes aus einem verschachtelten Objekt extrahiert und sie in ein flaches, stark typisiertes Tupel legt, wie folgt:

type NestedRecord = Record<string, any>

type RecursiveGetIndex<
  TRecord extends NestedRecord,
  TPreviousIndices extends any[] = []
> = {
  [K in keyof TRecord]: TRecord[K] extends NestedRecord
    ? RecursiveGetIndex<
        TRecord[K],
        AppendToTuple<TPreviousIndices,K>
      >
    : AppendToTuple<TPreviousIndices, K>
}[keyof TRecord]

type AppendToTuple<T extends any[], U> = [...T, U] 

Es funktioniert einwandfrei, wenn es mit einer konkreten Instanziierung eines verschachtelten Objekttyps aufgerufen wird, z. B.:

type AnIndex = RecursiveGetIndex<{ foo: { bar: { baz: number}}}> 

Mit anderen Worten, wie erwartet, ist AnIndex als ["foo", "bar", "baz"] typisiert

Aber wenn ich versuche, den rekursiven Typ in einer Funktion zu verwenden, erhalte ich einen Rekursionsfehler:

function doSomething<T extends NestedRecord>(arg:T){
    type Nested = RecursiveGetIndex<T>
    const doMore = (n: Nested) => {
        console.log(n) //Type instantiation is excessively deep and possibly nested
    }
}

Das ergibt für mich irgendwie Sinn, weil TS wirklich nicht weiß, wie tief T rekursiv sein könnte.

Ist das richtig? Warum würde TS nicht einfach warten, bis es sieht, wie doSomething instanziiert wird, bevor es sich um Rekursion sorgt?

Und gibt es eine Möglichkeit, den Fehler zu vermeiden, wenn ich im Voraus weiß, dass das arg, das an die Funktion übergeben wird, nie das TS-Rekursionslimit (50, glaube ich) überschreiten wird. Kann ich Typescript das irgendwie mitteilen?

Ich habe Lösungen gesehen, die die Rekursion im Grunde durch die Verwendung eines abnehmenden Array-Typs begrenzen, z. B. diese SO-Antwort. Ist das hier das Beste, was ich tun kann?

Spielplatz mit dem Code.

Aktualisiert

Auf Vorschlag von Captain Yossarian funktionieren die folgenden Werke:

type RecursiveGetIndex<
  TRecord extends NestedRecord,
  TPreviousIndices extends any[] = [],
  TDepth extends number = 5
> = 10 extends TPreviousIndices["length"] ? TPreviousIndices : {
  [K in keyof TRecord]: TRecord[K] extends NestedRecord
    ? RecursiveGetIndex<
        TRecord[K],
        AppendToTuple<TPreviousIndices,K>
      >
    : AppendToTuple<TPreviousIndices, K>
}[keyof TRecord]

Der Fehler tritt jedoch erneut auf, wenn diese Zahl höher als 10 ist. Ich hätte gedacht, dass sie 50 sein sollte. Ist mein Typ rekursiver, als ich denke?

Aktualisierter Spielplatz.

Antwort #1

RecursiveGetIndex<NestedRecord> ist hier das eigentliche Problem, und das liegt daran, wie any für einen bedingten Typ funktioniert.

Da Ihr generischer Typ auf NestedRecord-Typen beschränkt ist, muss TypeScript versuchen, RecursiveGetIndex auf die Einschränkung innerhalb der Funktion anzuwenden, um Ihnen die Typüberprüfung zu ermöglichen. Dies bedeutet jedoch, dass TRecord[K] any ist, sodass die Bedingung TRecord[K] extends NestedRecord schließlich beide Zweige auswertet, und da ein Zweig RecursiveGetIndex auf dieselbe Weise erneut aufruft.

Sobald TRecord=any mit Ihrer ursprünglichen Implementierung ist, sucht es einfach ewig nach verschachtelten Schlüsseln. Sie können also eine Überprüfung auf any hinzufügen und in diesem Fall einfach zu ...any[] auflösen, damit es nicht in unendliche Rekursion gerät. playground

type NestedRecord = Record<string, any>

type RecursiveGetIndex<
  TRecord extends NestedRecord,
  TPreviousIndices extends any[] = []
> = {
   // first check for any with 0 extends <clearly not 0> and opt out of recursing with just any nested keys
   // optionally you could use ...unknown[] if you wanted to be safer.
  [K in keyof TRecord]: 0 extends TRecord[K]&1 ? [...TPreviousIndices, K, ...any[]] 
  : TRecord[K] extends NestedRecord
    ? RecursiveGetIndex<
        TRecord[K],
        AppendToTuple<TPreviousIndices,K>
      >
    : AppendToTuple<TPreviousIndices, K>
}[keyof TRecord]


type AppendToTuple<T extends any[], U> = [...T, U] 

type AnIndex = RecursiveGetIndex<{ foo: { bar: number, baz: any}}> 
//   ^? ["foo", "bar"] | ["foo", "baz", ...any[]]

function doSomething<T extends NestedRecord>(arg:T){
    type Nested = RecursiveGetIndex<T>
    const doMore = (n: Nested) => {
        console.log(n) //here n will work a lot like unknown
    }
}

Verwandte Artikel