Plantilla variada 2D de TypeScript

Sep 25 2020

Actualmente tengo una interfaz que usa un miembro de datos de una matriz de algún otro tipo de interfaz, que se puede determinar mediante una enumeración:

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: "" } ]
}

Según mi entendimiento, ahora puedo usar a1.datay a2.datacomo matrices, pero a1.dataconozco el tipo de cada elemento (¿entonces en realidad una tupla?)

Ahora quiero usar una matriz 2D para interface Alos datos de.

Pensé que lo siguiente funcionaría, pero no me permitiría indexar 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
    }
}

Mi solución alternativa actual es usar una unión de tipos, pero esto no me permite especificar los datos que quiero usando una plantilla.

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

¿Hay alguna forma de admitir los 2 métodos de autocompletar datacomo se describe anteriormente para una matriz 2D?

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

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

Respuestas

1 LindaPaiste Sep 26 2020 at 04:22

Esto me tomó un poco de reflexión, pero la solución es bastante sencilla porque ya has hecho la mayor parte del trabajo preliminar con tu original interface A.

Defina un tipo genérico DataArrayque tome una tupla de valores E como su genérico y devuelva una tupla de los E2Data correspondientes. Puede reformatear para que DataArraycontenga la lógica y la Ause, pero para simplificar, accedamos a la lógica existente desde A.

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

En lugar de utilizar una firma de índice anidada interface B, puede utilizar una única firma de índice y llamar al DataArraytipo para crear las matrices internas.

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

Ahora crea cualquier tipo de datos 2D mezclando y combinando E.

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

Editar Sé que no pidió esto, pero aquí se explica cómo manejar valores anidados más profundamente. (Con alguna orientación de esta respuesta sobre matrices recursivas).

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: ""}] ]]
}