Learn TypeScript
Using mapped type modifiers
Using mapped type modifiers
In this lesson, we will learn what a mapped type modifier is and how it is useful.
Understanding a mapped type modifier
We used a mapped type modifier in the last lesson. This was when we made the keys in the mapped type optional by using a question mark (?
) in front of the key's type annotation:
{ [K in keyof T]?: TypeName}
The question mark is called a modifier. Another modifier is readonly
:
{ readonly [K in keyof T]: TypeName}
The readonly
modifier makes a property in the mapped type readonly. We will learn more about readonly properties later in this course.
What if we have a type with optional keys and want to map this to a type that has required keys? What if we have a type with readonly properties and want to map it to a type with writable properties? Well, we can use the -
symbol before the modifier to denote that the modifier should be removed if it exists.
Using the -
symbol before a ?
will map to a required key:
{ [K in keyof T]-?: TypeName}
Using the -
symbol before the readonly
keyword will map to a writable property:
{ -readonly [K in keyof T]: TypeName}
An example
As an example, in the code editor below, we will create a mapped type to make all the keys required.
The code contains a Contact
type with a variable that uses this type. Notice that some of the properties within the Contact
type are optional. We are going to create a mapped type that will turn these into required properties.
- Let's begin to implement the mapped type by adding the following generic type alias:
type RequiredProperties<T> = {};
- Add the mapping of the keys to our mapped type:
type RequiredProperties<T> = { [K in keyof T]-?: string;};
We have used the -
symbol before the ?
symbol to turn the keys into being required.
- Update the
bob
variable to use our mapped type:
const bob: RequiredProperties<Contact> = { name: "Bob",};
A type error occurs on bob
. Why is this so?.
- Add an
email
property tobob
:
const bob: RequiredProperties<Contact> = { name: "Bob", email: "bob@somewhere.com",};
The type error is resolved.
Great!
- Let's add another property to the
Contact
type:
type Contact = { ... age?: number;}
- Let's add a value for
age
tobob
:
const bob: RequiredProperties<Contact> = { name: "Bob", email: "bob@somewhere.com", age: 30,};
A type error occurs on age
in bob
. Why is this so?.
- We can resolve the type error by updating
RequiredProperties
as follows:
type RequiredProperties<T> = { [K in keyof T]-?: T[K];};
We have changed the key type from string
to T[K]
. This gets the corresponding type from the type being mapped from. It is called a lookup type or sometimes an indexed access type.
The type errors are now resolved.
What is the type of the age
property in bob
now?.
Neat!
We have actually just created a type that already exists as a standard utility type called Required
. The definition of Required
is below:
type Required<T> = { [P in keyof T]-?: T[P];};
This is the same as the mapped type we created apart from its name.
Summary
Mapped type modifiers add flexibility in mapped types to make properties required and writable.