Within your code, you are utilizing key remapping in mapped types, a concept distinct from a type assertion. Despite both employing the as
keyword, they serve different purposes. It's merely coincidental that they share this keyword.
A standard non-remapped mapped type format is {[T in KK]: V<T>}
, where it defines a type parameter T
iterating over the union members of key-like type KK
(required to be a subtype of PropertyKey
). Each union member T
results in a property with key T
and value V<T>
. The output value can be any function of type parameter T
, while the key type is confined to just T
.
In contrast, key remapping allows the type parameter to iterate over any union, computing both keys and values. The format is
{[T in U as K<T>]: V<T>}
, where it iterates over union members of type
U
, assigning properties with keys based on
K<T>
and values as
V<T>
.
In your provided example:
type Foo = "a" | "b" | 1 | 2;
type Bar = {
[K in Foo as number]: any
}
This is considered a degenerate case, neglecting Foo
when determining the key type. Instead, the key type is fixed as number
. Thus, the output features a single number
index signature with a value type of any
.
To illustrate a more realistic use case, consider defining the key as a template literal type dependent on K
:
type Baz = {
[K in Foo as `key_${K}`]: any
}
/* type Baz = {
key_a: any;
key_b: any;
key_1: any;
key_2: any;
} */
For an instance involving iteration over a non-key type:
type KVPair = { k: "a", v: string } | { k: "b", v: number } | { k: "c", v: boolean };
type Qux = { [T in KVPair as T["k"]]: T["v"] };
/* type Qux = {
a: string;
b: number;
c: boolean;
} */
This feature, unrelated to type assertions, offers significant utility.
Explore the code in TypeScript Playground