I am a novice in typescript, The following example of as
confuses me.
type foo = "a" | "b" | 1 | 2;
type bar = {
[k in foo as number]: any
}
The example is type checked. and bar
type is translated into
type bar = {
[x: number]: any
}
why k in foo as number
can be translated into x: number
?
CodePudding user response:
The as
keyword allow us to create type assertions.
In
type bar = {
[k in foo as number]: any
}
k
should be keyof foo
, but since you are using as
to asserts the type with number
, bar
will be
type bar = {
[k: number]: any
}
For example, take this little function:
const handlePost = async (request: Request) => {
const requestBody = await request.formData();
const name = requestBody.get("name");
const type = requestBody.get("type");
...
};
In this example, name
and type
are of type FormDataEntryValue | null
so we should check if they are a File
, string
or null
.
If we are pretty sure that the name
attribute in our form is correct, and that we are sending a plain string, we could do the following:
const handlePost = async (request: Request) => {
const requestBody = await request.formData();
const name = requestBody.get("name") as string;
const type = requestBody.get("type") as string;
...
};
Now name
and type
are both string
.
CodePudding user response:
Your code is using key remapping in mapped types. This is not a type assertion. Both key remapping and type assertions can use the as
keyword, but they are for quite different purposes; it is almost coincidental that they use the same keyword.
A normal non-remapped mapped type is of the form {[T in KK]: V<T>}
which defines a type parameter T
that iterates over the union members of the keylike type KK
(which must be a subtype of PropertyKey
, a.k.a. string | number | symbol
), and then for each of these union members T
, the output has a property with key T
and value V<T>
(where V
is some type function, like type V<T> = {x: T}
). So the value type can be any function of the type parameter T
, but the key type is restricted to be just T
itself. (Because the type parameter is required to be keylike, the name of the type parameter often reflects that by being something like K
for Key, or P
for Property.) This is very useful, but sometimes people want to compute the keys as well as the values.
Key remapping lets you have the type parameter iterate over any union whatsoever, (not just keylike types), and then you compute the keys and the values from that type parameter. It's of the form {[T in U as K<T>]: V<T>}
which defines a type parameter T
that iterates over the union members of the type U
, and then for each of these union members T
, the output has a property with key K<T>
(which must be a keylike type function, like type K<T extends string> = `key_${T}`
) and value V<T>
.
Your example
type Foo = "a" | "b" | 1 | 2;
type Bar = {
[K in Foo as number]: any
}
is a degenerate case, since you are iterating over Foo
but completely ignoring it when computing the key type. Instead, the key type is number
, no matter what K
is. So the output will have a single number
index signature with value type any
.
If you wanted a slightly more realistic example, you could compute the key as a template literal type that depends on K
, like this:
type Baz = {
[K in Foo as `key_${K}`]: any
}
/* type Baz = {
key_a: any;
key_b: any;
key_1: any;
key_2: any;
} */
And if you want an example where you iterate over a non-key type, it could look like this:
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 is quite a powerful feature, which again, has essentially nothing to do with type assertions.