Overview
TypeScript 2.1 introduced mapped types, a powerful addition to the type system. In essence, mapped types allow you to create new types from existing ones by mapping over property types. Each property of the existing type is transformed according to a rule that you specify. The transformed properties then make up the new type.
Using mapped types, you can capture the effects of methods such as Object.freeze() in the type system. After an object has been frozen, it’s no longer possible to add, change, or remove properties from it. Let’s see how we would encode that in the type system without using mapped types:
 | 
 | 
We’re defining a Point interface that contains the two properties x and y. We’re also defining another interface, FrozenPoint, which is identical to Point, except that all its properties have been turned into read-only properties using the readonly keyword.
The freezePoint function takes a Point as a parameter, freezes it, and returns the same object to the caller. However, the type of that object has changed to FrozenPoint, so its properties are statically typed as read-only. This is why TypeScript errors when attempting to assign 42 to the x property. At run-time, the assignment would either throw a TypeError (in strict mode) or silently fail (outside of strict mode).
While the above example compiles and works correctly, it has two big disadvantages:
- 
We need two interfaces. In addition to the
Pointtype, we had to define theFrozenPointtype so that we could add thereadonlymodifier to the two properties. When we changePoint, we also have to changeFrozenPoint, which is both error-prone and annoying. - 
We need the
freezePointfunction. For each type of object that we want to freeze in our application, we have to define a wrapper function that accepts an object of that type and returns an object of the frozen type. Without mapped types, we can’t statically typeObject.freeze()in a generic fashion. 
Thanks to TypeScript 2.1, we can do better.
Modeling Object.freeze() with Mapped Types
Let’s now see how Object.freeze() is typed within the lib.d.ts file that ships with TypeScript:
 | 
 | 
The method has a return type of Readonly<T> — and that’s a mapped type! It’s defined as follows:
 | 
 | 
This syntax may look daunting { 使人畏惧的 } at first, so let’s disassemble { 分解 } it piece by piece:
- We’re defining a generic 
Readonlytype with a single type parameter namedT. - Within the square brackets, we’re using the 
keyofoperator.keyof Trepresents all property names of typeTas a union of string literal types. - The 
inkeyword within the square brackets signals that we’re dealing with a mapped type.[P in keyof T]: T[P]denotes that the type of each propertyPof typeTshould be transformed toT[P]. Without thereadonlymodifier, this would be an identity transformation. - The type 
T[P]is a lookup type. It represents the type of the propertyPof the typeT. - Finally, the 
readonlymodifier specifies that each property should be transformed to a read-only property. 
Because the type Readonly<T> is generic, Object.freeze() is typed correctly for every type we provide for T. We can now simplify our code from before:
 | 
 | 
Much better!
An Intuitive Explanation of the Syntax for Mapped Types
Here’s another attempt to explain roughly { 粗略地 } how the type mapping works, this time using our concrete Point type as an example. Note that the following is only an intuitive approach for explanatory { 解释的,说明的 } purposes that doesn’t accurately reflect the resolution algorithm used by TypeScript.
Let’s start with a type alias:
 | 
 | 
We can now substitute the type Point for each occurrence of the generic type T in Readonly<T>:
 | 
 | 
Now that we know that T is Point, we can determine the union of string literal types that keyof Point represents:
 | 
 | 
The type P represents each of the properties x and y. Let’s write those as separate properties and get rid of the mapped type syntax:
 | 
 | 
Finally, we can resolve the two lookup types and replace them by the concrete types of x and y, which is number in both cases:
 | 
 | 
And there you go! The resulting ReadonlyPoint type is identical to the FrozenPoint type that we created manually.
More Examples for Mapped Types
We’ve seen the Readonly<T> type that is built into the lib.d.ts file. In addition, TypeScript defines additional mapped types that can be useful in various situations. Some examples:
 | 
 | 
And here are two more examples for mapped types that you could write yourself if you have the need for them:
 | 
 | 
You can have fun with mapped types and combine their effects:
 | 
 | 
Good stuff!
Practical Use Cases for Mapped Types
I want to finish this post by motivating how mapped types could be used in practice to more accurately type frameworks and libraries. More specifically, I want to look at React and Lodash:
- React: A component’s 
setStatemethod allows you to update either the entire state or only a subset of it. You can update as many properties as you like, which makes thesetStatemethod a great use case forPartial<T>. - Lodash: The 
pickutility function allows you to pick a set of properties from an object. It returns a new object containing only the properties you picked. That behavior can be modeled usingPick<T>, as the name already suggests. 
Note that at the time of writing, none of the above changes have been made to the corresponding type declaration files on DefinitelyTyped.