I am creating a tuple, which is indexable by an enum. To my surprise, this works, as TS understands length of enums and tuples. However, when writing the type of the tuple, i find no way to relate it to the enum:
enum E {
ITEM1,
ITEM2,
// ITEM3,
}
declare const e: E;
declare const myTuple: [string, string];
myTuple[e];
Uncommenting ITEM3
will cause an error, i'd have to also add more elements to the tuple, manually. However, the tuple is being generated. I'd much prefer, if editing the enum would suffice (as always, have one central place for changes).
Sadly, afaik TS has no way to convert object keys (e.g. those of an enum) to a tuple-type, even if just the count.
Is there really no better way than when adding an element in the enum, to also add one to the tuple type by hand? I am considering the apocalyptic cast of as unknown as string[] & Record<E, string>
. While that is extremely unclean, and destroys .length
, or operations on its type, it's the best i can find so far. Perhaps the sour apple of having two places to edit is still preferable though.
tl;dr; how can i get a tuple-type, which is always indexable by an enum (has the same length)?
CodePudding user response:
Here's one approach:
type EnumToTuple<T extends number, V, A extends V[] = []> =
`${T}` extends keyof A ? A : EnumToTuple<T, V, [...A, V]>
EnumToTuple<T, V>
is a tail-recursive conditional type that starts with the empty tuple and keeps appending V
to it until it has the "same length" as the T
numeric enum. Well, it keeps appending until the tuple has keys corresponding to the stringified versions of the T
enum.
So for this:
enum E {
ITEM1,
ITEM2,
ITEM3,
}
declare const e: E;
type ETuple = EnumToTuple<E, string>;
// type ETuple = [string, string, string]
the EnumToTuple<E, string>
type keeps appending string
to the accumulated tuple until "0" | "1" | "2"
are its keys, which first happens at [string, string, string]
. And that means a value of type ETuple
is known to have a string
property at keys of type E
:
declare const myTuple: ETuple
myTuple[e].toUpperCase(); // okay