13
头图

This article will introduce the Mapped Type in TypeScript in detail. After reading this article, you will learn the following knowledge points:

  • The relationship between mappings in mathematics and mapping types in TS;
  • Application of mapping types in TS;
  • Application of mapping type modifiers in TS;

Next, we will start with the introduction of "mapping in mathematics".

The TypeScript version used in this article is v4.6.2 .

If you are not familiar with TypeScript, you can read the following resources:

  1. A rare TS study guide (1.8W words)
  2. Awesome tutorial for getting started with TypeScript

1. What is mapping?

When learning the TypeScript type system, try to learn as much as possible by analogy with sets in mathematics, such as union types in TypeScript, similar to unions in mathematics, etc.

In mathematics, mapping refers to the relationship between the elements of two sets of elements corresponding to each other , such as the following figure:

image.png
(Source: https://baike.baidu.com/item/%E6%98%A0%E5%B0%84/20402621 )

Mapping can be understood as a function, as shown in the figure above, when we need to convert the elements of set A to the elements of set B, we can do the mapping through the f function, such as the elements of set A 1 corresponds to the element in set B 2 .
In this way, the reuse of the mapping process can be well realized.

2. What is the mapping type in TypeScript?

1. Concept introduction

Mapping types in TypeScript are similar to mapping in mathematics, and can convert elements of a collection to elements of a new collection, except that TypeScript mapping types map one type to another .

In our actual development, we often need to convert all properties of a type to optional types. At this time, you can directly use the Partial tool type in TypeScript:

 type User = {
  name: string;
  location: string;
  age: number;
}

type User2 = Partial<User>;
/*
  User2 的类型:
  
  type User2 = {
      name?: string | undefined;
      location?: string | undefined;
      age?: number | undefined;
  }
*/

In this way, we realize the mapping of the ---f13e4b16000c46e601a200c143433a4d User type to the User2 type, and convert all attributes in the User type to optional types.

image.png

2. Implementation method

The syntax for TypeScript mapped types is as follows:

 type TypeName<Type> = {
  [Property in keyof Type]: boolean;
};

Since we can convert all attributes of a specified type into optional types through a very simple implementation of the Partial tool type, what is the content principle?

We can hover the mouse over the name of Partial in the editor, and we can see the editor prompt as follows:

image.png

Break down each of these parts:

  • type Partial<T> : defines a type alias Partial and a generic T ;
  • keyof T : get generic type T key keyof operator , can be understood as an array);
 type User = {
  name: string;
  location: string;
  age: number;
}

type KeyOfUser = keyof User; // "name" | "location" | "age"
  • in : similar to for...in in JS in , used to traverse the public property name of the target type;
  • T[P] : It is an index access type (also called a lookup type), and the generic type T in P type is obtained, similar to the way of accessing objects in JS;
  • ?: set type value to optional type;
  • { [P in keyof T] ?: T[P] | undefined} : traversing keyof T joint return type, and is defined by P variables received through the returned value of each of its alternative type T[P] .

In this way, the Partial tool type is realized. This method of operation is very important and is an important basis for TypeScript type gymnastics later.

For the exercises of type gymnastics, if you are interested, you can read this article:
"How many of these 30 TS practice questions can you answer correctly? 》 https://juejin.cn/post/7009046640308781063

3. Application of Mapping Types

TypeScript mapping types are often used to reuse some types of operations. For example, TypeScript currently supports 21 tool types. Some of our commonly used type operations are defined as these tool types to facilitate developers to reuse these types.

See the official documentation for all supported tool types:
https://www.typescriptlang.org/docs/handbook/utility-types.html

Let's pick a few commonly used tool types and see how mapping types are used in their implementation.

In the process of learning TypeScript, it is recommended to practice and learn more in the official Playground:
https://www.typescriptlang.org/en/play

1. Required Required attributes

Used to set all properties of the type as required properties .

The implementation is as follows:

 type Required<T> = {
    [P in keyof T]-?: T[P];
};

How to use:

 type User = {
  name?: string;
  location?: string;
  age?: number;
}

type User2 = Required<User>;
/*
  type User2 = {
      name: string;
      location: string;
      age: number;
  }
*/

const user: User2 = {
  name: 'pingan8787',
  age: 18
}
/*
  报错:
  Property 'location' is missing in type '{ name: string; age: number; }'
  but required in type 'Required<User>'.
*/

The -? symbol here can be temporarily understood as "converting an optional attribute into a required attribute", and the next section will introduce these symbols in detail.

2. Readonly read-only property

Used to make the type of all properties read-only, i.e. the type cannot be reassigned.

The implementation is as follows:

 type Readonly<T> = {
  readonly [P in keyof T]: T[P];
}

How to use:

 type User = {
  name?: string;
  location?: string;
  age?: number;
}

type User2 = Readonly<User>;
/*
  type User2 = {
      readonly name?: string | undefined;
      readonly location?: string | undefined;
      readonly age?: number | undefined;
  }
*/

const user: User2 = {
  name: 'pingan8787',
  age: 18
}

user.age = 20;
/*
  报错:
  Cannot assign to 'age' because it is a read-only property.
*/

3. Pick select the specified attribute

Used to select the specified property from the specified type and return it .

The implementation is as follows:

 type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
}

Use as follows:

 type User = {
  name?: string;
  location?: string;
  age?: number;
}

type User2 = Pick<User, 'name' | 'age'>;
/*
  type User2 = {
      name?: string | undefined;
      age?: number | undefined;
  }
*/

const user1: User2 = {
  name: 'pingan8787',
  age: 18
}

const user2: User2 = {
  name: 'pingan8787',
  location: 'xiamen', // 报错
  age: 18
}
/*
  报错
  Type '{ name: string; location: string; age: number; }' is not assignable to type 'User2'.
  Object literal may only specify known properties, and 'location' does not exist in type 'User2'.
*/

4. Omit ignores the specified attribute

The effect is similar to that of Pick tool type . The specified attribute can be ignored from the specified type and returned.

The implementation is as follows:

 type Omit<T, K extends string | number | symbol> = {
  [P in Exclude<keyof T, K>]: T[P];
}

How to use:

 type User = {
  name?: string;
  location?: string;
  age?: number;
}

type User2 = Omit<User, 'name' | 'age'>;
/*
  type User2 = {
      location?: string | undefined;
  }
*/

const user1: User2 = {
  location: 'xiamen',
}

const user2: User2 = {
  name: 'pingan8787', // 报错
  location: 'xiamen'
}
/*
  报错:
  Type '{ name: string; location: string; }' is not assignable to type 'User2'.
  Object literal may only specify known properties, and 'name' does not exist in type 'User2'.
*/

5. Exclude excludes the specified type from the union type

Used to exclude the specified type from the specified union type .

The implementation is as follows:

 type Exclude<T, U> = T extends U ? never : T;

How to use:

 type User = {
  name?: string;
  location?: string;
  age?: number;
}

type User2 = Exclude<keyof User, 'name'>;
/*
  type User2 = "location" | "age"
*/

const user1: User2 = 'age';
const user2: User2 = 'location';
const user3: User2 = 'name';  // 报错
/*
  报错:
  Type '"name"' is not assignable to type 'User2'.
*/

Fourth, the application of the mapping modifier

When customizing the mapping type, we can use two modifiers of the mapping type to achieve our needs:

  • readonly modifier: set the specified property to read-only type ;
  • ? modifier: set the specified property to an optional type ;

In the previous introduction of Readonly and Partial tool types have been used:

 type Readonly<T> = {
  readonly [P in keyof T]: T[P];
}

type Partial<T> = {
  [P in keyof T]?: T[P] | undefined;
}

Of course, you can also operate on modifiers:

  • + add modifier (used by default);
  • - remove modifier;

for example:

 type Required<T> = {
    [P in keyof T]-?: T[P]; // 通过 - 删除 ? 修饰符
};

It can also be used in front of:

 type NoReadonly<T> = {
  -readonly [P in keyof T]: T[P]; // 通过 - 删除 readonly 修饰符
}

V. Summary

This article takes the mapping in mathematics as an entry point, introduces the TypeScript mapped type (Mapped Type) in detail, and introduces the application of the mapped type and the application of the modifier.

When learning the TypeScript type system, try to learn as much as possible by analogy with sets in mathematics, such as union types in TypeScript, similar to unions in mathematics, etc.

Learning the mapping type is a very important foundation for the next type of gymnastics~~

References

  1. TypeScript Documentation - Mapped Types: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
  2. TypeScript utility types: https://www.typescriptlang.org/docs/handbook/utility-types.html

pingan8787
3.2k 声望4.1k 粉丝

🌼 前端宝藏小哥哥