Welcome to the Mastering TypeScript series. Part of the TYPESCRIPT Series, these articles introduce the core knowledge and techniques of TypeScript in the form of animated Animations .
OK HATE
Problem
Have you used Partial, Required, Readonly, and Pick utility types?
If you want to master them and create your own utility types
, don’t miss the content mentioned in this article.
Creating a User
type is a common scenario in everyday work. Here, we can use TypeScript to define a User type where all keys are required.
1 2 3 4 5 6 7 | type User <span class="token operator">=</span> <span class="token punctuation">{</span> name <span class="token operator">:</span> string <span class="token punctuation">;</span> password <span class="token operator">:</span> string <span class="token punctuation">;</span> address <span class="token operator">:</span> string <span class="token punctuation">;</span> phone <span class="token operator">:</span> string <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> |
Normally, for Type User declared, we only allow to modify some information. At this point, we can define a new UserPartial type that represents the type of User object to update, where all keys are optional.
1 2 3 4 5 6 7 | type UserPartial <span class="token operator">=</span> <span class="token punctuation">{</span> name <span class="token operator">?</span> <span class="token operator">:</span> string <span class="token punctuation">;</span> password <span class="token operator">?</span> <span class="token operator">:</span> string <span class="token punctuation">;</span> address <span class="token operator">?</span> <span class="token operator">:</span> string <span class="token punctuation">;</span> phone <span class="token operator">?</span> <span class="token operator">:</span> string <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> |
For the user information viewing scenario, we expect that all keys in the object type corresponding to the user object are read-only ( Readonly ). For this request, we can define a read-only User type.
1 2 3 4 5 6 7 | type ReadonlyUser <span class="token operator">=</span> <span class="token punctuation">{</span> readonly name <span class="token operator">:</span> string <span class="token punctuation">;</span> readonly password <span class="token operator">:</span> string <span class="token punctuation">;</span> readonly address <span class="token operator">:</span> string <span class="token punctuation">;</span> readonly phone <span class="token operator">:</span> string <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> |
Reviewing the three identified user-related types, you will see that they contain a lot of duplicate code.
So how can we reduce duplicate code in the above categories? The answer is that you can use Mapped Types, which are generic Types that can be used to map the original object type to a new object type.
Mapped Type
The syntax for mapping types is as follows:
The P in K
case is similar to the in
statement in JavaScript, used to iterate over all types in type K, and a variable of type T, used to represent any type in TypeScript.
You can also use additional read-only modifiers and question marks (?) during mapping. Modified syntaxes are added and removed, respectively, by adding plus(+) and minus(-) prefixes. The default is to use the plus sign if no prefix is added.
We can now summarize the syntax of common Mapping types.
1 2 3 4 5 6 7 | <span class="token punctuation">{</span> <span class="token punctuation">[</span> <span class="token constant">P</span> <span class="token keyword">in</span> <span class="token constant">K</span> <span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">}</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span> <span class="token constant">P</span> <span class="token keyword">in</span> <span class="token constant">K</span> <span class="token punctuation">]</span> <span class="token operator">?</span> <span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">}</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span> <span class="token constant">P</span> <span class="token keyword">in</span> <span class="token constant">K</span> <span class="token punctuation">]</span> <span class="token operator">-</span> <span class="token operator">?</span> <span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">}</span> <span class="token punctuation">{</span> readonly <span class="token punctuation">[</span> <span class="token constant">P</span> <span class="token keyword">in</span> <span class="token constant">K</span> <span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">}</span> <span class="token punctuation">{</span> readonly <span class="token punctuation">[</span> <span class="token constant">P</span> <span class="token keyword">in</span> <span class="token constant">K</span> <span class="token punctuation">]</span> <span class="token operator">?</span> <span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">}</span> <span class="token punctuation">{</span> <span class="token operator">-</span> readonly <span class="token punctuation">[</span> <span class="token constant">P</span> <span class="token keyword">in</span> <span class="token constant">K</span> <span class="token punctuation">]</span> <span class="token operator">?</span> <span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">}</span> |
After looking at the syntax of Mapped Types
, now let’s go to some examples.
Let’s see how to redefine the UserPartial
type using Mapped Types.
1 2 3 4 5 | type MyPartial <span class="token operator"><</span> <span class="token constant">T</span> <span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span> <span class="token constant">P</span> <span class="token keyword">in</span> keyof <span class="token constant">T</span> <span class="token punctuation">]</span> <span class="token operator">?</span> <span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">[</span> <span class="token constant">P</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> type UserPartial <span class="token operator">=</span> MyPartial <span class="token operator"><</span> User <span class="token operator">></span> <span class="token punctuation">;</span> |
In the above code, we define Mapped Types MyPartial
and then use it to map the User type to the UserPartial
type. The keyof operator is used to get all keys of a type and its return type is associative. The variable of type P changes to a different type with each traversal, T[P]
, which is similar to the Properties access syntax and is used to get the value type corresponding to a Properties of the object type.
Take a look at the image showing the complete execution of Mapped Types MyPartial
, if not clear, you can watch more than once to get a deeper understanding of Mapped Types TypeScript.
TypeScript 4.1 allows us to remap keys in Mapped Types using an as
clause. Its syntax is as follows:
1 2 3 4 5 6 | type MappedTypeWithNewKeys <span class="token operator"><</span> <span class="token constant">T</span> <span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span> <span class="token constant">K</span> <span class="token keyword">in</span> keyof <span class="token constant">T</span> <span class="token keyword">as</span> NewKeyType <span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">[</span> <span class="token constant">K</span> <span class="token punctuation">]</span> <span class="token comment">// ^^^^^^^^^^^^^</span> <span class="token comment">// New Syntax!</span> <span class="token punctuation">}</span> |
Where the type NewKeyType
must be a subtype of string | number | symbol union type. Using the as
clause, we can define the Getters utility type and generate the corresponding Getter type for the object type.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | type Getters <span class="token operator"><</span> <span class="token constant">T</span> <span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span> <span class="token constant">K</span> <span class="token keyword">in</span> keyof <span class="token constant">T</span> <span class="token keyword">as</span> <span class="token template-string"><span class="token template-punctuation string">`</span> <span class="token string">get</span> <span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span> Capitalize <span class="token operator"><</span> string <span class="token operator">&</span> <span class="token constant">K</span> <span class="token operator">></span> <span class="token interpolation-punctuation punctuation">}</span></span> <span class="token template-punctuation string">`</span></span> <span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token constant">T</span> <span class="token punctuation">[</span> <span class="token constant">K</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> <span class="token keyword">interface</span> <span class="token class-name">Person</span> <span class="token punctuation">{</span> name <span class="token operator">:</span> string <span class="token punctuation">;</span> age <span class="token operator">:</span> number <span class="token punctuation">;</span> location <span class="token operator">:</span> string <span class="token punctuation">;</span> <span class="token punctuation">}</span> type LazyPerson <span class="token operator">=</span> Getters <span class="token operator"><</span> Person <span class="token operator">></span> <span class="token punctuation">;</span> <span class="token comment">// {</span> <span class="token comment">// getName: () => string;</span> <span class="token comment">// getAge: () => number;</span> <span class="token comment">// getLocation: () => string;</span> <span class="token comment">// }</span> |
In the above code, since the type returned by keyof T
can contain Symbol type and Capitalize utility type. It requires the type to be handled to be a subtype of string
, so it is necessary to filter the type using the &
operator.
Also, in the process of remapping the Keys, we can filter the Keys by returning never type
.
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token comment">// Remove the 'kind' property</span> type RemoveKindField <span class="token operator"><</span> <span class="token constant">T</span> <span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span> <span class="token constant">K</span> <span class="token keyword">in</span> keyof <span class="token constant">T</span> <span class="token keyword">as</span> Exclude <span class="token operator"><</span> <span class="token constant">K</span> <span class="token punctuation">,</span> <span class="token string">"kind"</span> <span class="token operator">></span> <span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">[</span> <span class="token constant">K</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> <span class="token keyword">interface</span> <span class="token class-name">Circle</span> <span class="token punctuation">{</span> kind <span class="token operator">:</span> <span class="token string">"circle"</span> <span class="token punctuation">;</span> radius <span class="token operator">:</span> number <span class="token punctuation">;</span> <span class="token punctuation">}</span> type KindlessCircle <span class="token operator">=</span> RemoveKindField <span class="token operator"><</span> Circle <span class="token operator">></span> <span class="token punctuation">;</span> <span class="token comment">// type KindlessCircle = {</span> <span class="token comment">// radius: number;</span> <span class="token comment">// };</span> |
After reading this article, I’m sure you understand the function of mapped types
and how to implement some utility types
inside TypeScript.
Mapping is one of the foundational and core knowledge so that you can go further on your way to conquering other advanced concepts in Typescript.
Roundup
As always, I hope you enjoyed this article and learned something new.
Thank you and see you in the next posts!
If you find this blog interesting, please give me a like and subscribe to support me. Thank you.