While Typescript has been mostly smooth sailing for me, I keep encountering a roadblock.
Let's say I have a type that can be one of several strings:
type ResourceConstant = 'A' | 'B' | 'C';
Now I want to create an object to store the quantity of each resource (essentially a mapping of ResourceConstant
to number
):
let x: { [key: ResourceConstant]: number } = {};
>>> error TS1337: An index signature parameter type cannot be a union type. Consider using a mapped object type instead.
So what exactly is a "mapped object type"? I've come across information about "mapped types", but it doesn't seem to address this issue directly. Perhaps they suggest using Record
:
let x: Record<ResourceConstant, number> = {};
>>> error TS2740: Type '{}' is missing the following properties from type 'Record<ResourceConstant, number>': U, L, K, Z, and 80 more.
Adding Partial
seems to make it work:
let x: Partial<Record<ResourceConstant, number>> = {};
It's functional, but definitely not elegant.
Moving on, let's try to apply this somewhere:
for (let res in x) {
terminal.send(res, x[res]);
}
>>> error TS2345: Argument of type 'string' is not assignable to parameter of type 'ResourceConstant'.
It appears that the type information is lost as objects are always indexed by strings. To resolve this, I'll explicitly specify that it is a ResourceConstant:
for (let res: ResourceConstant in x) {
>>> error TS2404: The left-hand side of a 'for...in' statement cannot use a type annotation.
Perhaps using as
could enforce the type:
for (let res as ResourceConstant in x) {
>>> error TS1005: ',' expected
Or maybe casting with the <>
syntax?
for (let res<ResourceConstant> in x) {
>>> error TS1005: ';' expected
Neither approach works. It seems I need to introduce a secondary variable to enforce the type:
for (let res in x) {
let res2 = res as ResourceConstant;
terminal.send(res2, x[res2]);
}
This solves the issue, but it feels cumbersome.
Despite knowing that switching to new Map
would be the correct solution, we're accustomed to using plain objects in JavaScript. Additionally, if working with an existing codebase, should I really have to convert everything to Map
just for Typescript? What am I overlooking when dealing with plain objects?