DAY 28 OF 30
Mapped types
Yesterday, in one sentence
Writing several signatures for a single function, each pairing a set of parameters with its own return type. One function definition sits underneath and covers all of them, but its signature is not offered to callers. TypeScript picks the first signature that matches the arguments at the call site.
Did you say:
- ✓several signatures for one function
- ✓each pairs a set of parameters with its own return type
- ✓one function definition underneath, its signature not offered to callers
- ✓TypeScript picks the first signature that matches the call
Interview question
Pick<T, K> and Omit<T, K>?Both build a new object type from T using a set of property names K. Pick
keeps only those properties. Omit keeps everything except those properties.
Did you say:
- ✓both take a type and a set of property names
- ✓`Pick` keeps only the named properties
- ✓`Omit` removes them and keeps the rest
Today's syntax is three things you already know, stacked. Worth a quick look at each before they combine.
keyofkeyof gives a union of a type's property
names.
type Hero = {
name: string;
level: number;
};
type HeroKey = keyof Hero;
// "name" | "level"
An indexed access typeIndexed Access Type looks up the type of one property by name.
type Level = Hero["level"];
// number
And in, inside the square brackets of the syntax below, loops over a union. This is
not the JavaScript in operator used for narrowingNarrowing. It
only appears here, and it maps over every member of the union, creating a property for
each one, to produce a new object type.
A mapped type puts all three together.
// type TypeName = { [Key in Union]: ValueType }
type Hero = {
name: string;
level: number;
};
type ReadonlyHero = {
readonly [K in keyof Hero]: Hero[K];
};
// { readonly name: string; readonly level: number }
keyof Hero is the union, so the block runs once with K as "name" and once with
K as "level". Each pass creates a property with that name, and Hero[K] reads that
property's original type.
Add ? or readonly in front of the key and every generated property gets that
modifier. This is how Partial is written.
// map over each property of T, keep its type, make it optional
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
type PartialHero = MyPartial<Hero>;
// { name?: string; level?: number }
A minus sign in front takes the modifier away instead, so -? makes optional
properties required. This is how Required is written.
// map over each property of T, keep its type, remove the optional marker
type MyRequired<T> = {
[K in keyof T]-?: T[K];
};
type Draft = {
name?: string;
level?: number;
};
type FullHero = MyRequired<Draft>;
// { name: string; level: number }
A mapped type can only go in a type aliasType Alias. There is no way to write one on an interfaceInterface.
Check yourself
Powers<Hero> here?
type Hero = {
flight: string
healing: string
}
type Powers<T> = {
[K in keyof T]: boolean
}
The mapped type loops over the keys of T and keeps the names, but the value type is fixed as boolean rather than read from T[K].
A type that builds an object type by looping over a union of keys and giving each
key a type, written [K in keyof T]. You can add or remove ? and readonly as
you go. The built in utility types like Partial and Readonly are written this
way.
Did you say:
- ✓builds an object type by looping over a union of keys
- ✓written `[K in keyof T]`
- ✓can add or remove `?` and `readonly` as it goes
- ✓the built in utility types are written this way