TypeScript: Generischer Typ eines Objekt-Eigenschafts-Tupels eines bestimmten Wertetyps
Ich frage mich, ob es möglich ist, einen generischen Typ zu definieren, der ein Tupel der Länge 2 [O, K] ist, und ihn AccessorPair zu nennen, wobei der erste Typ O ein Objekt und der zweite Typ K ein Schlüssel für dieses Objekt ist, sodass O[K] einen bestimmten Typ T erweitert.
Zum Beispiel,
const foo: AccessorPair<number> = [new Array(), 'length']
const bar: AccessorPair<boolean> = [[true, false], 0]
Mein erster Versuch ist
type AccessorPair<T> = T extends (infer O)[infer K] ? [O, K] : never
aber es führt nur zu "Typ 'K' kann nicht verwendet werden, um den Typ 'O' zu indizieren.ts(2536)"
Ich habe versucht, eine Umgehungslösung zu finden, indem ich
type ValueOf<O, P extends keyof O> = O[P]
type AccessorPair<T> = T extends ValueOf<infer O, infer P> ? [O, P] : never
und es löst sich immer in „nie“ auf.
Ist eine solche Definition zu vage, um von TypeScript unterstützt zu werden? Gibt es ein Programmierkonzept, das ich im Hinblick auf eine solche Typisierung nicht kenne? Jede Diskussion ist willkommen!
Antwort #1
Zunächst müssen wir zwei Definitionen unterscheiden: arrays und tuples in TypeScript.
Betrachten Sie array als eine Liste unbekannter Länge, etwas wie vector in Rust.
Betrachten Sie tuple als eine Liste bekannter Länge zur Kompilierzeit.
Um erlaubte Schlüssel für das [true, false]-Tupel zu handhaben und es gibt nur zwei davon 0 | 1, müssen wir einen kleinen Hilfstyp schreiben:
type IsTuple<T> =
(T extends Array<any> ?
(T['length'] extends number
? (number extends T['length']
? false
: true)
: true)
: false)
{
type _ = IsTuple<[1, 2]> // true
type __ = IsTuple<number[]> // false
type ___ = IsTuple<{ length: 2 }> // false
}
type AllowedTupleLength<
T extends ReadonlyArray<unknown>,
Length extends number = never
> = T extends readonly [infer _, ...infer Tail]
? AllowedTupleLength<Tail, Length | Tail['length']>
: T extends readonly []
? Length
: never;
type Result = AllowedTupleLength<[0,0]> // 1 | 0
Jetzt brauchen wir ein Dienstprogramm, das bedingt erlaubte Schlüssel für Tupel und Array zurückgibt:
type ComputeKeys<Tuple extends any[]> =
IsTuple<Tuple[0]> extends true ? AllowedTupleLength<Tuple[0]> : keyof Tuple[0]
ComputeKeys gibt 1|0 für [true, false] zurück und alle Array-Schlüssel für ein reguläres Array.
Übrigens, willst du zulassen, dass forEach, reduce, map Schlüssel als zweites Element im Tupel verwendet werden?
Also müssen wir alle Nicht-Array-Objekte behandeln. Lassen Sie uns den Json-Typ mit allen serialisierbaren Typen in einer Vereinigung erstellen:
type Json = | string | number | boolean | { [prop: string]: Json } | Array<Json>
type IsTuple<T> =
(T extends Array<any> ?
(T['length'] extends number
? (number extends T['length']
? false
: true)
: true)
: false)
type AllowedTupleLength<
T extends ReadonlyArray<unknown>,
Length extends number = never
> = T extends readonly [infer _, ...infer Tail]
? AllowedTupleLength<Tail, Length | Tail['length']>
: T extends readonly []
? Length
: never;
type ComputeKeys<Tuple extends any[]> =
IsTuple<Tuple[0]> extends true ? AllowedTupleLength<Tuple[0]> : keyof Tuple[0]
function handleTuple<
Elem extends Exclude<Json, any[]>, // overload signature for non array serializable values
Tuple extends Elem[]
>(tuple: [...Tuple, keyof Tuple[0]]): [...Tuple]
function handleTuple<
Elem,
NestedTuple extends Elem[],
Tuple extends [...NestedTuple][]
>(tuple: [...Tuple, ComputeKeys<Tuple>]): [...Tuple]
function handleTuple(tuple: unknown[]) {
return tuple
}
handleTuple([[true, false], 1]) // ok
handleTuple([[true, false], 2]) // expected error, index is too big
handleTuple([new Array(), 'length']) // ok
handleTuple([new Array(), 2]) // allowed because we don't know exact length of array
handleTuple([{ name: 'John', age: 32 }, 'name']) // allowed because we don't know exact length of array
Spielplatz
Übrigens, da du nicht alle erlaubten Objekte in einem Tupel kennst, ist es unmöglich, es ohne zusätzliche Funktion zu machen.
