Modèle variadique 2D Typescript
J'ai actuellement une interface utilisant un membre de données d'un tableau d'un autre type d'interface, qui peut être déterminé par une énumération:
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: "" } ]
}
Selon ma compréhension, je peux maintenant utiliser a1.dataet en a2.datatant que tableaux, mais a1.dataconnaît le type de chaque élément (donc en fait un tuple?)
Maintenant, je veux utiliser un tableau 2D pour interface Ales données de.
Je pensais que le ci-dessous fonctionnerait, mais cela ne me permettrait pas d'indexer en E2Datautilisant 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
}
}
Ma solution de contournement actuelle consiste à utiliser une union de types, mais cela ne me permettra pas de spécifier les données souhaitées à l'aide d'un modèle.
interface B {
data: (E2Data[E.a] | E2Data[E.b] | E2Data[E.c])[][];
}
Existe-t-il un moyen de prendre en charge les 2 méthodes de saisie semi-automatique datacomme décrit ci-dessus pour un tableau 2D?
let b1: A<[ [E.a], [E.b] ]> = {
data: [ [{ a: "" }], [{ b: "" }] ]
}
let b2: A = {
data: [ [<E2Data[E.a]>{ a: "" }], [ <E2Data[E.b]>{ b: "" }] ]
}
Réponses
Cela m'a pris un peu de réflexion, mais la solution est en fait assez simple car vous avez déjà fait la plupart des démarches avec votre original interface A.
Définissez un type générique DataArrayqui prend un tuple de valeurs E comme générique et retourne un tuple de l'E2Data correspondant. Vous pouvez reformater afin que DataArraycontient la logique et l' Autilise, mais pour plus de simplicité, accédons simplement à la logique existante à partir de A.
type DataArray<T extends E[]> = A<T>['data'];
Au lieu d'utiliser une signature d'index imbriquée dans interface B, vous pouvez utiliser une seule signature d'index et appeler le DataArraytype pour créer les tableaux internes.
interface B<T extends E[][] = E[][]> {
data: {
[i in keyof T]: T[i] extends E[] ? DataArray<T[i]> : never
}
}
Vous créez maintenant n'importe quel type de données 2D en mélangeant et en faisant correspondre E.
let b: B<[[E.a], [E.b, E.c]]> = {
data: [ [{ a: "" }], [{ b: "" }, {c: ""}] ]
}
Edit Je sais que vous ne l'avez pas demandé, mais voici comment gérer des valeurs plus profondément imbriquées. (Avec quelques conseils de cette réponse sur les tableaux récursifs).
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: ""}] ]]
}