I'm a beginner of typescript. I find a difference between keyof any
and string | number | symbol
in MappedType, but I'm not sure what the difference is between these two writing styles.
type T = keyof any;
//string | number | symbol
type T1 = { [P in keyof any]: number };
//{[x: string]: number}
type T2 = { [P in string | number | symbol]: number }
//{ [x: string]: number; [x: number]: number; [x: symbol]: number; }
Thank you all for your help!
I hope there are experts who can help me answer.
When you write a mapped type of the form {[K in keyof T]: ⋯}
, with in keyof
present, TypeScript treats it as a homomorphic mapped type (see What does "homomorphic mapped type" mean?) and performs some operations specific to the type in T
. For normal cases this involves preserving the optional/readonly modifiers from the properties of T
in the resulting mapped type.
On the other hand if the mapped type just looks like {[K in KK]: ⋯}
where KK
is some arbitrary keylike type that doesn't start with keyof
, then TypeScript doesn't have any idea what object type T
the KK
keys might have come from, and so it just maps over the keys in KK
without any dependence on T
.
This is all documented behavior, especially for normal cases where T
is a generic type parameter that is instantiated with an object type. When T
is a generic type parameter that is instantiated with an array/tuple type, or with a primitive type, the homomorphic mapping will do different special things compared to the non-homomoprhic mapping.
In your example, though, the type T
is any
, and this seems to be a corner case. TypeScript is treating {[K in keyof any]: ⋯}
as a homomorphic mapped type over any
, and it produces {[k: string]: ⋯}
as implemented in microsoft/TypeScript#19185, which is responsible for your T1
type.
This was implemented before numeric/symbol keys in keyof
and mapped types as implemented in microsoft/TypeScript#23592, and very much before symbol or union index signatures as implemented in microsoft/TypeScript#44512. At the time there was no concept of {[k: string]: ⋯; [k: number]: ⋯; [k: symbol]: ⋯}
as you see in your T2
type. So over time the behavior of the homomorphic and non-homomorphic mapped type have diverged a bit for keyof any
.
It's possible one could file a feature request asking for this discrepancy to be addressed, although it really seems like a corner case that few people run into or have trouble with. Neither behavior is obviously wrong, and the inconsistency is just one of many inconsistencies in TypeScript, so the fact that they differ is not enough to make it a bug (e.g., "different code does different things" is not necessarily a bug).
Anyway, assuming you really wanted to get the version with the three index signatures and that the homomorphic mapping was unintentional, you can break the connection by using some alias for keyof any
so that in keyof
doesn't appear. You can apparently do this with simple parentheses:
type T3 = { [P in (keyof any)]: number };
/* type T3 = {
[x: string]: number;
[x: number]: number;
[x: symbol]: number;
} */
But instead of keyof any
I'd recommend using the TypeScript-provided PropertyKey
type, which is declared in the TypeScript library as
declare type PropertyKey = string | number | symbol;
So you get
type T4 = { [P in PropertyKey]: number };
/* type T4 = {
[x: string]: number;
[x: number]: number;
[x: symbol]: number;
} */
The only time you'd really want to write keyof any
instead of PropertyKey
is if you have code that needs to work both before and after number
and symbol
keys were supported in mapped types, meaning code both before and after TypeScript 2.9. Which is quite unlikely at this point. If you're only looking at recent TypeScript versions, then just write PropertyKey
instead of keyof any
.