enum Animals {
CAT,
DOG,
PARROT,
SHEEP,
SALMON
}
The following type:
type AnimalsToMappedType = {
[K in keyof typeof Animals]: K
}
Results in:
type AnimalsToMappedType = {
readonly [x: number]: number;
readonly CAT: "CAT";
readonly DOG: "DOG";
readonly PARROT: "PARROT";
readonly SHEEP: "SHEEP";
readonly SALMON: "SALMON";
}
But if you add parentheses around keyof typeof Animals
:
type AnimalsToMappedType = {
[K in (keyof typeof Animals)]: K
}
The result is:
type AnimalsToMappedType = {
CAT: "CAT";
DOG: "DOG";
PARROT: "PARROT";
SHEEP: "SHEEP";
SALMON: "SALMON";
}
QUESTION
What are the parentheses doing that removes readonly [x: number]: number;
?
See microsoft/TypeScript#40206 for an authoritative answer.
A mapped type of the form {[K in keyof XXX]: YYY}
is considered a homomorphic mapped type, where TypeScript sees in keyof
and recognizes that you're transforming some other type XXX
, and can therefore use information about the original type that isn't present in the results of keyof
. On the other hand, a mapped type of the similar form {[K in (keyof XXX)]: YYY}
is not considered homomorphic. The parentheses cause TypeScript to first compute keyof XXX
, and then the mapped types iterates over these results without knowing or remembering anything about XXX
. So in keyof XXX
keeps information about XXX
around that in (keyof XXX)
discards.
This difference is most commonly seen when transforming a type with optional and readonly
properties. A homomorphic mapped type will preserve the optional/readonly
, even though such information is not visible via keyof
. For example, if XXX
is {a?: string, readonly b: number}
then {[K in keyof XXX]: YYY}
will produce {a?: YYY, readonly b: YYY}
, whereas {[K in (keyof XXX): YYY}
will produce {a: YYY, b: YYY}
.
In your example, you're operating on a numeric enum object, which has reverse mappings. If Animals.CAT === 0
then Animals[0] === "CAT"
. TypeScript models this by adding a numeric index signature to the enum object, but this index signature is suppressed when you use keyof
. So while typeof Animals
might look like { [k: number]: string; CAT: 0 }
, keyof typeof Animals
is just "CAT"
and not number | "CAT"
. So a homomorphic mapped type over a numeric enum will have a numeric index signature, whereas a non-homomorphic mapped type over keyof
a numeric enum will not.