Modello variadico 2D dattiloscritto

Sep 25 2020

Al momento ho un'interfaccia che utilizza un membro dati di un array di un altro tipo di interfaccia, che può essere determinato da un enum:

enum E { a, b, c }
interface E2Data {
    [E.a]: { a: string },
    [E.b]: { b: string },
    [E.c]: { c: string }
}

interface A<T extends E[] = E[]> {
    data: { [i in keyof T]: T[i] extends E ? E2Data[T[i]] : never }
}

// Can give the enums in the template, and get autocomplete in the data.
let a1: A<[E.a, E.b]> = {
    data: [{ a: "" }, { b: "" }]
};

// Or can get autocomplete with type assertion on elements.
// Ideally this would infer template types too, but apparently not.
let a2: A = {
    data: [ <E2Data[E.a]>{ a: "" }, <E2Data[E.b]>{ b: "" } ]
}

Secondo la mia comprensione, ora posso usare a1.datae a2.datacome array, ma a1.dataconosce il tipo di ogni elemento (quindi in realtà una tupla?)

Ora voglio usare un array 2D per interface Ai dati di.

Ho pensato che quanto segue avrebbe funzionato, ma non mi avrebbe permesso di indicizzare E2Datausando T[i][j].

interface B<T extends E[][] = E[][]> {
    data: {
        [i in keyof T]:
        T[i] extends E[] ? {
            [j in keyof T[i]]:
            T[i][j] extends E ? E2Data[T[i][j]] : never }
        //                      ^^^^^^^^^^^^^^^
        // Type 'T[i][j]' cannot be used to index type 'E2Data'.
        : never
    }
}

La mia soluzione attuale consiste nell'usare un'unione di tipi, ma questo non mi consente di specificare i dati che desidero utilizzando un modello.

interface B {
    data: (E2Data[E.a] | E2Data[E.b] | E2Data[E.c])[][];
}

C'è un modo per supportare i 2 metodi di completamento automatico datacome descritto sopra per un array 2D?

let b1: A<[ [E.a], [E.b] ]> = {
    data: [ [{ a: "" }], [{ b: "" }] ]
}

let b2: A = {
    data: [ [<E2Data[E.a]>{ a: "" }], [ <E2Data[E.b]>{ b: "" }] ]
}

Risposte

1 LindaPaiste Sep 26 2020 at 04:22

Questo mi ha richiesto un po 'di riflessione, ma la soluzione è in realtà piuttosto semplice perché hai già fatto la maggior parte del lavoro sulle gambe con l'originale interface A.

Definire un tipo generico DataArrayche accetta una tupla di valori E come generico e restituisce una tupla del corrispondente E2Data. Potresti riformattare in modo che DataArraycontenga la logica e la Ausi, ma per semplicità accediamo semplicemente alla logica esistente da A.

type DataArray<T extends E[]> = A<T>['data'];

Invece di usare una firma di indice annidata in interface B, puoi usare una singola firma di indice e chiamare il DataArraytipo per creare gli array interni.

interface B<T extends E[][] = E[][]> {
    data: {
        [i in keyof T]: T[i] extends E[] ? DataArray<T[i]> : never
    }
}

Ora crei qualsiasi tipo di dati 2D mescolando e abbinando E.

let b: B<[[E.a], [E.b, E.c]]> = {
    data: [ [{ a: "" }], [{ b: "" }, {c: ""}] ]
}

Modifica So che non l'hai chiesto, ma ecco come gestire valori annidati più profondamente. (Con alcune indicazioni da questa risposta sugli array ricorsivi).

interface Nested<T> extends Array<Nested<T> | T> {
}

type NestedData<T extends Nested<E>> = { 
    [i in keyof T]: T[i] extends E ? E2Data[T[i]] : T[i] extends Nested<E> ? NestedData<T[i]> : never
}

interface C<T extends Nested<E>> {
    data: NestedData<T>
}

let c: C<[E.a, [E.b, [E.c]]]> = {
    data: [ { a: "" }, [{ b: "" }, [{c: ""}] ]]
}