How to better understand TS generics? Mapped Types

Matan Cohen
Wix Engineering
Published in
7 min readMar 6, 2023

This is the third blog post in the series, if you have not read the first two go ahead:

In this article we will explore the powerful TS feature, Mapped Types.

For understanding Mapped Types, we will first need to understand:

Indexed Access Types

Index access types are types that are used to look up a specific type property in other types using square brackets [].

Here we are accessing the property ‘age’ and receiving the type number.

We can also access the types using a union:

Remember that the access type can be either number, string or union.

Index access types are very powerful and I'll show you some neat ways to use them.

  1. Index access type — Object to a union.

We can use index access types to create a union type from const object.

How does it work?
1. On line 5 we use the as const to make all fields of Prizes read-only.
2. Then we get its type using typeof .
3. We create a union from the type we just created using keyof .
4. We use index access type to create a union of its values.
5. profit!

Index access types can do even more!

2. Access nested types using another set of brackets.

3. Access arrays: types

Now that we understand what index access type can do, there is one more thing that we need to know before using them with Generics.

Index Signatures

Index signatures are used when we don’t know ahead of time the name of the type properties in object types, but we do know the type of values.

For example, using string as keys:

Here the keys (name of employees) can be any string and the value has to be a number.

We can also use number type as keys

numberArray and stringNumberArray are both of the same type.

Because when indexing with a number JS will actually convert the number into a string.

So, indexing with the number zero is the same as indexing with the string '0'.

Index signatures are a useful way to describe a dictionary, but they have one big constraint: they force all the “value” types to be the same.

x has the type number that is not assignable to string.

We can solve this problem using a union as the value for the index signature.

We are ready to learn one of the greatest tools in TypeScript.

Photo by Dariusz Sankowski on Unsplash

Mapped Types

With index signatures we defined for an arbitrary key its type value.

What if we want a type object with only specific keys?

This is where mapped types are very handy!

For understanding the basics of Mapped Types, we are going to use the following example.

We are building a function that is coloring a specific box side,
We have a Color type: (yes, we support only three).

type Color = 'red' | 'blue' |'green'

We have Sides type

type Sides = 'right' | 'top' | 'bottom' | 'left'

And we want to define a Box type, which is a map between side and color.

We can try to solve this with index signatures:

With index signatures we can only use arbitrary keys, which means that box variable is valid Box type (even with ‘lef’ typo).

We want our box to have all Sides as a key and have no other keys.

Using Mapped Types:

Having a mental model of looping helps understanding Mapped types.

  1. The in keyword in the mapped type indicates an “iteration process”.
  2. To the right of in is the union we “iterate” over. In this case: Sides.
  3. To the left of in is the item we are “iterating” with. In this case: Side.

And now using Box type

We must have all Sides as keys and we can’t add any key other than Sides. If we do, it will cause a type error.

As this is an article about generics. Let’s see how Generics and mapped types work together.

Photo by Immo Wegmann on Unsplash

Record using Mapped Types

The last example is a very specific way to create a map between specific keys to a value type.

type Box = {[Side in Sides] : Color}

We want to create a more generic and reusable type and for this we will use generics type parameters.

We will call our generic type OwnRecord. The first type parameter will be KeyProps ,which will represent the union we “iterate” over.

We will put a constraint on our generic parameter, KeyProps extends string, which means we expect it to be a string.

We are missing the second parameter, the generic value.

The generic value does not need to have any constraints as we want the user of this type to determine any Value type they want.

That’s it, we have our very own generic record. Let’s build a Box with it!

Box is exactly the same type as before, OwnRecord is generic and reusable, and similar to the built-in type Record.

The only difference is K extends keyof any.

type KeyOfAny = keyof any // string | number | symbol

This means the built-in Record type allows also to have number, symbol and string as key.

You might think we would stop here, huh? No way!

We want to add index access types to the mix.

Mapped Types with index access types

It’s time to combine the knowledge we acquired to build even more powerful types.

We want to create our own custom addEventListner that allows us to listen to specific event types.

We want to allow listening only to click, mouseenterand mouseleave.

customEventListner needs to have more constrained types, for the eventType and for the listener.

To achieve this, we will use the built-in type of WindowEventMap and we will create a subset of it.

WindowEventMap is a mapping between an event name and the event type.
E.g click: MouseEvent .

Let’s use mapped types and index access types to achieve this.

First, we iterate over the union using the mapped type. Then, we use the key to receive the value from the object type using index access type.

We got CustomEventMap but we want to create a more reusable type that will help us do it in an easier way in the future.

First, pull the union that we iterate out to a type parameter.

We introduced the type parameter Keys for the union and constrained it to be keyof WindowEventMap .
This means that only union of keys of WindowEventMap are acceptable as the type parameter.

Now we can use any subset of keys from WindowEventMap which is pretty generic.

Can we make it even more generic? Of course, we can!

We will parametrize WindowEventMap .

We introduce a second type parameter named ObjType.
This is the type from which we are creating a sub object type.

Pay attention that Keys are constrained to be the keyof ObjType, therefore we are guaranteed to have in the result type only keys and value from ObjType .

Now we can create any sub object type we want! We have just built the fantastically useful TypeScript built-in Pick.

Which will generate the exact same type.

And now we are ready to have a typesafe customEventListner

We allow listening to subsets of events, and as a bonus, the listner is type safe, which is a great success!

What if instead of picking keys from an object type, we want to omit keys from an object type?

Try to figure it out for yourself before you keep scrolling.

Photo by Kenny Eliason on Unsplash

We can use PartObj that we already built in order to solve this, the first part of it should probably stay identical.

We still need a type parameter for the union and the object type.

type OmitObj<ObjType,Keys extends keyof ObjType> =

Now we need to figure out which set of keys we need to iterate over.
We can’t simply iterate over keys, that will give us the same result.

We need to iterate on all other keys in the object type except the one in Keys.
Luckily we already know the tool for this job (from previous articles): Exclude.

We are excluding the Keys from all the keys of ObjType (create a union without Keys) and iterating over them, which results in a subset of the object type with omit of certain keys.

And clearly this is another TypeScript built-in Omit<T, K>.

Sum up

In this article we explored many features of TypeScript, starting with index signature and index signature types.
We used that knowledge to learn about the powerful Mapped types.

We have used Mapped types to build our own version of the built-in types Pick and Omit.

Equipped with this knowledge I hope you can use Generics in a more versatile and useful way.

I have used the TypeScript docs, which has a lot more to offer.

And this great course in FM:

Keep on learning!

--

--

Matan Cohen
Wix Engineering

Frontend Tech lead at Wix, on the path of mastering functional programming.