Advanced TypeScript: Mapped Types With Index Signatures
Welcome to the Mapped Types With Index Signatures lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
We've seen mapped types on objects with explicit properties, like
{email: string}. We can also apply mapped types to index signature types. Remember that index signatures are types like{[userName: string]: number}. That type allows any string as a property name, but all properties must have numbers as their values.In the example below, we have a
LoginCountstype. It uses an index signature to count how many times each user has logged in. We only know the users' names at runtime, not at compile time, so we have to allow the type's property names to be any string.>
type LoginCounts = {[userName: string]: number};const loginCounts: LoginCounts = {Amir: 5,Betty: 11,};loginCounts.Betty;Result:
11
With that type, a user who has never logged in would presumably have a login count of 0. However, suppose that some other part of the system expects this to be
nullrather than 0. We can build a new mapped type that works likeLoginCounts, but also allowsnull.>
type Nullable<T> = {[K in keyof T]: T[K] | null};type LoginCounts = {[userName: string]: number};const loginCounts: Nullable<LoginCounts> = {Amir: 5,Betty: null,};loginCounts.Betty;Result:
null
Many of TypeScript's built-in utility types are implemented as mapped types.
Partialis one example. We can use those utility types on types with index signatures, like we did above with our own mapped type. Normally, ourLoginCountstype doesn't allow us to useundefinedas a property value. But if we wrap it inPartialthen it will allowundefined. (Remember thatPartialmakes every property optional by unioning its type withundefined.)>
type LoginCounts = {[userName: string]: number};const loginCounts: LoginCounts = {Amir: 5,Betty: undefined,};loginCounts.Betty;Result:
type error: Type 'undefined' is not assignable to type 'number'.
>
type LoginCounts = {[userName: string]: number};const loginCounts: Partial<LoginCounts> = {Amir: 5,Betty: undefined,};loginCounts.Betty;Result:
undefined
Finally, mapped types also work on types that mix regular properties and index signatures.
>
type LoginCounts = {Amir: number[userName: string]: number};const loginCounts: Partial<LoginCounts> = {Amir: 5,Betty: undefined,};loginCounts.Betty;Result:
undefined