Filtering Properties by key
Mapped types work great with indexed access types, because the index is used when defining the value type.
type PartOfWindow = {
[Key in 'document' | 'navigator' | 'setTimeout']: Window[Key];
};
// type PartOfWindow = {
// document: Document;
// navigator: Navigator;
// setTimeout: (
// handler: TimerHandler,
// timeout?: number | undefined,
// ...arguments: any[]
// ) => number;
// };
The mapped type "loops over" all of the possible keys and determines the appropriate value type for each key.
Use with Type Parameters (Generics)
Rework the example above using type parameters.
Let the caller define which keys they'd like to use.
We'll call this type PickWindowProperties
because you get to specify which properties from Window
you'd like:
type PickWindowProperties<Keys extends keyof Window> = {
[Key in Keys]: Window[Key];
};
type PartOfWindow = PickWindowProperties<
'document' | 'navigator' | 'setTimeout'
>;
// type PartOfWindow = {
// document: Document;
// navigator: Navigator;
// setTimeout: (
// handler: TimerHandler,
// timeout?: number | undefined,
// ...arguments: any[]
// ) => number;
// };
<Keys extends keyof Window>
limits the options of what Keys
can be...it can only be a union of literal types (strings) that are keys on the Window
interface.
Also take note of [Key in Keys]
. That's the mapped type that "loops" over Keys
and each time provides that Key
as an indexed access type to Window
.
Those loops of Window[Key]
with the different Key
values provides the specific value types for PartOfWindow
.
Make it Fully Generalized
Let's generalize it one step further by allowing this type to work on anything, not just Window
.
Because this is no longer a type that exclusively works with Window
, we'll rename this type to PickProperties
.
// Completely Generic
type PickProperties<Keys extends keyof ValueType, ValueType> = {
[Key in Keys]: ValueType[Key];
};
// Used with `Window`
type PartOfWindow = PickProperties<
'document' | 'navigator' | 'setTimeout',
Window
>;
// type PartOfWindow = {
// readonly document: Document;
// readonly navigator: Navigator;
// setTimeout: (
// handler: TimerHandler,
// timeout?: number | undefined,
// ...arguments: any[]
// ) => number;
// };
Pick - TS utility type
The PickProperties
mapped typed created above matches the built-in TS utility type Pick
exactly:
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<K extends keyof T, T> = {
[P in K]: T[P];
};
Mapping Modifiers - More TS Utility Types
There are a couple of final things we can do to the properties as we create each type: set whether the value placed there should be readonly
and/or optional
.
This is fairly straightforward, and you can see the use of the ?
and readonly
in the three other built-in TypeScript utility types below.
If there's a -
to the left of readonly
or ?
in a mapped type, that indicates removal of this modifier instead of application of the modifier.
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
/**
* Make all properties in T required
*/
type Required<T> = {
[P in keyof T]-?: T[P];
};
/**
* Make all properties in T readonly
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
There is no built-in TypeScript utility type for readonly
removal, but you could implement one if you needed to (not necessarily a good idea though)
type NotReadonly<T> = {
-readonly [P in keyof T]: T[P];
};
From the Intermediate TypeScript↗ course on FEM↗ taught by Mike North↗.