Modelo de variadic 2D Typescript

Sep 25 2020

Atualmente, tenho uma interface que usa um membro de dados de uma matriz de algum outro tipo de interface, que pode ser determinado por um 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: "" } ]
}

De acordo com meu entendimento, agora posso usar a1.datae a2.datacomo matrizes, mas a1.dataconheço o tipo de cada elemento (na verdade, uma tupla?)

Agora eu quero usar um array 2D para interface Aos dados de.

Achei que o seguinte funcionaria, mas não me permitiria 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
    }
}

Minha solução alternativa atual é usar uma união de tipo, mas isso não me permite especificar os dados que desejo usando um modelo.

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

Existe uma maneira de apoiar os 2 métodos de preenchimento automático dataconforme descrito acima para uma matriz 2D?

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

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

Respostas

1 LindaPaiste Sep 26 2020 at 04:22

Isso me levou a pensar um pouco, mas a solução é na verdade bem direta porque você já fez a maior parte do trabalho braçal com o original interface A.

Defina um tipo genérico DataArrayque recebe uma tupla de valores E como seu genérico e retorna uma tupla do E2Data correspondente. Você poderia reformatar de forma que DataArraycontenha a lógica e a Ause, mas para simplificar, vamos apenas acessar a lógica existente de A.

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

Em vez de usar uma assinatura de índice aninhada em interface B, você pode usar uma única assinatura de índice e chamar o DataArraytipo para criar os arrays internos.

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

Agora você cria qualquer tipo de dados 2D misturando e combinando E.

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

Editar Eu sei que você não pediu por isso, mas veja como lidar com valores aninhados mais profundamente. (Com alguma orientação desta resposta sobre matrizes 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: ""}] ]]
}