type Foo<T> = {
// pretend we have a type definition for Q here
}[keyof T];
What does the [keyof T]
do at the end there?
Short Answer
When placed at the end of a type declaration, [keyof T]
creates an indexed access type in which the index to lookup is an indexed type query on T
.
That is a mouthful. Here it is in two steps:
- First create a union type of the property names of
T
. - e.g.
"one" | "two" | "three"
- then get the types of those properties from type
Q
. - e.g.
string | number | boolean
keyof T
does the first step and []
does the second step.
Explanation
The ~ TypeScript 2.1 release notes say that an "indexed type query keyof T
yields the type of permitted property names for T
." The use of the word "yield" makes an indexed type query sound like a generator of the properties names of T
.
// given a type like this
interface Model {
one: string,
two: number,
three: boolean,
}
// the generator produces a union type of property names
type Props = keyof Model; // "one" | "two" | "three"
When keyof T
is in brackets []
at the end of a type, it is acting as an index access type. An index access type gets the type of a property by name.
type TypeOfOne = Model["one"]; // string
type TypeOfTwo = Model["two"]; // number
type TypeOfThree = Model["three"]; // boolean
An index access type can also lookup the type of a union of property names:
// string | number | boolean
type TypeOfOneTwoOrThree = Model["one" | "two" | "three"];
That is what { /* some type Q */ }[keyof T]
is doing.
- generate a union type of property names of
T
, - turn that into a union type of those properties' types in
Q
.
Manual Type Expansion
// initial example
type Foo<T> = {
[K in keyof T]: T[K]
}[keyof T];
// usage
interface Model {
one: string,
two: number,
three: boolean,
}
// result
type FooModel = Foo<Model>; // string | number | boolean
Expanding the initial example manually looks like this:
// replace T with Model
type FooModel1 = {
[K in keyof Model]: Model[K]
}[keyof Model];
// replace keyof Model with the keys
type FooModel2 = {
[K in "one" | "two" | "three"]: Model[K]
}["one" | "two" | "three"];
// replace K with each key instance
type FooModel3 = {
one: Model["one"];
two: Model["two"];
three: Model["three"];
}["one" | "two" | "three"];
// replace Model["key"] with its type
type FooModel4 = {
one: string;
two: number;
three: boolean;
}["one" | "two" | "three"];
// expand the lookup type union
type FooModel6 = string | number | boolean;