Ketikan template variadic 2D

Sep 25 2020

Saat ini saya memiliki antarmuka yang menggunakan anggota data array dari beberapa jenis antarmuka lainnya, yang dapat ditentukan dengan 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: "" } ]
}

Menurut pemahaman saya, sekarang saya dapat menggunakan a1.datadan a2.datasebagai array, tetapi a1.datamengetahui jenis setiap elemen (jadi sebenarnya tupel?)

Sekarang saya ingin menggunakan array 2D untuk interface Adata.

Saya pikir cara di bawah ini akan berhasil, tetapi tidak memungkinkan saya untuk mengindeks E2Datamenggunakan 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
    }
}

Solusi saya saat ini adalah dengan menggunakan tipe gabungan, tetapi ini tidak mengizinkan saya menentukan data yang saya inginkan menggunakan templat.

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

Apakah ada cara untuk mendukung 2 metode pelengkapan otomatis dataseperti yang dijelaskan di atas untuk larik 2D?

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

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

Jawaban

1 LindaPaiste Sep 26 2020 at 04:22

Ini membuat saya berpikir, tetapi solusinya sebenarnya cukup mudah karena Anda telah melakukan sebagian besar kerja keras dengan yang asli interface A.

Tentukan tipe generik DataArrayyang menggunakan tupel nilai E sebagai generiknya dan mengembalikan tupel dari E2Data yang sesuai. Anda dapat memformat ulang sehingga DataArrayberisi logika dan Amenggunakannya, tetapi untuk kesederhanaan mari kita mengakses logika yang ada dari A.

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

Alih-alih menggunakan tanda tangan indeks bersarang di interface B, Anda bisa menggunakan tanda tangan indeks tunggal dan memanggil DataArraytipe untuk membuat array dalam.

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

Sekarang Anda membuat tipe data 2D dengan mencampur dan mencocokkan E.

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

Sunting Saya tahu Anda tidak meminta ini, tetapi berikut ini cara menangani nilai bertingkat yang lebih dalam. (Dengan beberapa panduan dari jawaban ini tentang array rekursif).

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