搞懂 TypeScript 中的映射类型(Mapped Types)

简介: 本文从数学中的映射作为切入点,详细介绍 TypeScript 映射类型(Mapped Type)并介绍映射类型的应用和修饰符的应用。

image.png

本文会和大家详细介绍 TypeScript 中的映射类型(Mapped Type),看完本文你将学到以下知识点:

  • 数学中的映射和 TS 中的映射类型的关系;
  • TS 中映射类型的应用;
  • TS 中映射类型修饰符的应用;

接下来会先从「数学中的映射」开始介绍。

本文使用到的 TypeScript 版本为 v4.6.2

如果你对 TypeScript 还不熟悉,可以看下面几篇资料:

  1. 一份不可多得的 TS 学习指南(1.8W字)
  2. 了不起的 TypeScript 入门教程

一、什么是映射?

在学习 TypeScript 类型系统时,尽量多和数学中的集合类比学习,比如 TypeScript 中的联合类型,类似数学中的并集等。

在数学中,映射是指两个元素的集合之间元素相互对应的关系,比如下图:

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

可以将映射理解为函数,如上图,当我们需要将集合 A 的元素转换为集合 B 的元素,可以通过 f函数做映射,比如将集合 A 的元素 1对应到集合 B 中的元素 2
这样就能很好的实现映射过程的复用

二、TypeScript 中的映射类型是什么?

1. 概念介绍

TypeScript 中的映射类型和数学中的映射类似,能够将一个集合的元素转换为新集合的元素,只是 TypeScript 映射类型是将一个类型映射成另一个类型

在我们实际开发中,经常会需要一个类型的所有属性转换为可选类型,这时候你可以直接使用 TypeScript 中的 Partial工具类型:

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

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

这样我们就实现了将 User类型映射成 User2类型,并且将 User类型中的所有属性转为可选类型。

image.png

2. 实现方法

TypeScript 映射类型的语法如下:

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

我们既然可以通过 Partial工具类型非常简单的实现将指定类型的所有属性转换为可选类型,那其内容原理又是如何?

我们可以在编辑器中,将鼠标悬停在 Partial名称上面,可以看到编辑器提示如下:

image.png

拆解一下其中每个部分:

  • type Partial<T>:定义一个类型别名 Partial和泛型 T
  • keyof T:通过 keyof操作符获取泛型 T中所有 key,返回一个联合类型(如果不清楚什么是联合类型,可以理解为一个数组);
type User = {
  name: string;
  location: string;
  age: number;
}

type KeyOfUser = keyof User; // "name" | "location" | "age"
  • in:类似 JS 中 for...in中的 in,用来遍历目标类型的公开属性名;
  • T[P]:是个索引访问类型(也称查找类型),获取泛型 TP类型,类似 JS 中的访问对象的方式;
  • ?:将类型值设置为可选类型;
  • { [P in keyof T] ?: T[P] | undefined}:遍历 keyof T返回的联合类型,并定义用 P变量接收,其每次遍历返回的值为可选类型的 T[P]

这样就实现了 Partial工具类型,这种操作方法非常重要,是后面进行 TypeScript 类型体操的重要基础。

关于类型体操的练习,有兴趣可以看看这篇文章:
《这 30 道 TS 练习题,你能答对几道?》 https://juejin.cn/post/7009046640308781063

三、映射类型的应用

TypeScript 映射类型经常用来复用一些对类型的操作过程,比如 TypeScript 目前支持的 21 种工具类型,将我们常用的一些类型操作定义成这些工具类型,方便开发者复用这些类型。

所有已支持的工具类型可以看下官方文档:
https://www.typescriptlang.org/docs/handbook/utility-types.html

下面我们挑几个常用的工具类型,看下其实现过程中是如何使用映射类型的。

在学习 TypeScript 过程中,推荐多在官方的 Playground 练习和学习:
https://www.typescriptlang.org/zh/play

1. Required 必选属性

用来将类型的所有属性设置为必选属性

实现如下:

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

使用方式:

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>'.
*/

这边的 -?符号可以暂时理解为“将可选属性转换为必选属性”,下一节会详细介绍这些符号。

2. Readonly 只读属性

用来将所有属性的类型设置为只读类型,即不能重新分配类型。

实现如下:

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

使用方式:

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 选择指定属性

用来从指定类型中选择指定属性并返回

实现如下:

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

使用如下:

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 忽略指定属性

作用类似与 Pick工具类型相反,可以从指定类型中忽略指定的属性并返回。

实现如下:

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

使用方式:

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 从联合类型中排除指定类型

用来从指定的联合类型中排除指定类型

实现如下:

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

使用方式:

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'.
*/

四、映射修饰符的应用

在自定义映射类型的时候,我们可以使用两个映射类型的修饰符来实现我们的需求:

  • readonly修饰符:将指定属性设置为只读类型
  • ?修饰符:将指定属性设置为可选类型

前面介绍 ReadonlyPartial工具类型的时候已经使用到:

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

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

当然,也可以对修饰符进行操作:

  • +添加修饰符(默认使用);
  • -删除修饰符;

比如:

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

也可以放在前面使用:

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

五、总结

本文从数学中的映射作为切入点,详细介绍 TypeScript 映射类型(Mapped Type)并介绍映射类型的应用和修饰符的应用。

在学习 TypeScript 类型系统时,尽量多和数学中的集合类比学习,比如 TypeScript 中的联合类型,类似数学中的并集等。

学好映射类型,是接下来做类型体操中非常重要的基础~~

参考资料

  1. TypeScript 文档-映射类型:https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
  2. TypeScript 工具类型: https://www.typescriptlang.org/docs/handbook/utility-types.html
目录
相关文章
|
2月前
|
设计模式 JavaScript 安全
TypeScript性能优化及代码质量提升的重要性、方法与策略,包括合理使用类型注解、减少类型断言、优化模块导入导出、遵循编码规范、加强代码注释等
本文深入探讨了TypeScript性能优化及代码质量提升的重要性、方法与策略,包括合理使用类型注解、减少类型断言、优化模块导入导出、遵循编码规范、加强代码注释等,旨在帮助开发者在保证代码质量的同时,实现高效的性能优化,提升用户体验和项目稳定性。
55 6
|
2月前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
52 2
|
2月前
|
JavaScript 安全 前端开发
TypeScript类型声明:基础与进阶
通过本文的介绍,我们详细探讨了TypeScript的基础与进阶类型声明。从基本数据类型到复杂的泛型和高级类型,TypeScript提供了丰富的工具来确保代码的类型安全和可维护性。掌握这些类型声明能够帮助开发者编写更加健壮和高效的代码,提高开发效率和代码质量。希望本文能为您在使用TypeScript时提供实用的参考和指导。
50 2
|
2月前
|
JavaScript 开发者
在 Babel 插件中使用 TypeScript 类型
【10月更文挑战第23天】可以在 Babel 插件中更有效地使用 TypeScript 类型,提高插件的开发效率和质量,减少潜在的类型错误。同时,也有助于提升代码的可理解性和可维护性,使插件的功能更易于扩展和升级。
|
2月前
|
JavaScript 前端开发 安全
TypeScript进阶:类型系统与高级类型的应用
【10月更文挑战第25天】TypeScript作为JavaScript的超集,其类型系统是其核心特性之一。本文通过代码示例介绍了TypeScript的基本数据类型、联合类型、交叉类型、泛型和条件类型等高级类型的应用。这些特性不仅提高了代码的可读性和可维护性,还帮助开发者构建更健壮的应用程序。
38 0
|
4月前
|
JavaScript
typeScript进阶(9)_type类型别名
本文介绍了TypeScript中类型别名的概念和用法。类型别名使用`type`关键字定义,可以为现有类型起一个新的名字,使代码更加清晰易懂。文章通过具体示例展示了如何定义类型别名以及如何在函数中使用类型别名。
56 1
typeScript进阶(9)_type类型别名
|
3月前
|
JavaScript 前端开发 安全
深入理解TypeScript:增强JavaScript的类型安全性
【10月更文挑战第8天】深入理解TypeScript:增强JavaScript的类型安全性
75 0
|
3月前
|
JavaScript 前端开发 开发者
深入理解TypeScript:类型系统与实用技巧
【10月更文挑战第8天】深入理解TypeScript:类型系统与实用技巧
|
4月前
|
存储 JavaScript
typeScript进阶(11)_元组类型
本文介绍了TypeScript中的元组(Tuple)类型,它是一种特殊的数组类型,可以存储不同类型的元素。文章通过示例展示了如何声明元组类型以及如何给元组赋值。元组类型在定义时需要指定数组中每一项的类型,且在赋值时必须满足这些类型约束。此外,还探讨了如何给元组类型添加额外的元素,这些元素必须符合元组类型中定义的类型联合。
65 0
|
4月前
|
JavaScript
typeScript进阶(10)_字符串字面量类型
本文介绍了TypeScript中的字符串字面量类型,这种类型用来限制变量只能是某些特定的字符串字面量。通过使用`type`关键字声明,可以确保变量的值限定在预定义的字符串字面量集合中。文章通过示例代码展示了如何声明和使用字符串字面量类型,并说明了它在函数默认参数中的应用。
51 0