深入了解TypeScript中的映射类型

目录
  • 1. 基本概念
    • (1)索引访问类型
    • (2)索引签名
    • (3)联合类型
    • (4)keyof 类型运算符
    • (5)元组类型
    • (6)条件类型
  • 2. 映射类型
    • (1)初体验
    • (2)概念
    • (3)实例
  • 3. 实用程序中的映射
    • (1)Partial
    • (2)Exclude
  • 4. 构建映射类型

DRY 原则(Don't repeat yourself)是软件开发中最重要的原则之一,即不要重复自己。应该避免在代码中的两个或多个地方存在重复的业务逻辑。

在 TypeScript 中,映射类型可以帮助我们避免编写重复的代码,它可以根据现有类型和定义的一些规则来创建新类型。下面就来看一下什么是映射类型以及如何构建自己的映射类型。

1. 基本概念

在介绍映射类型之前,先来看一些前置知识。

(1)索引访问类型

在 TypeScript 中,我们可以通过按名称查找属性来访问它的类型:

type AppConfig = {
  username: string;
  layout: string;
};

type Username = AppConfig["username"];

在这个例子中,通过 AppConfig 类型的索引 username 获取到其类型 string,类似于在 JavaScript 中通过索引来获取对象的属性值。

(2)索引签名

当类型属性的实际名称是未知的,但它们将引用的数据类型已知时,索引签名就很方便。

type User = {
  name: string;
  preferences: {
    [key: string]: string;
  }
};

const currentUser: User = {
  name: 'Foo Bar',
  preferences: {
    lang: 'en',
  },
};
const currentLang = currentUser.preferences.lang;

在上面的例子中,currentLang 的类型是 string 而不是 any。此功能与 keyof 运算符一起搭配使用是使映射类型成为可能的核心之一。

(3)联合类型

联合类型是两种或多种类型的组合。它表明值的类型可以是联合中包含的任何一种类型。

type StringOrNumberUnion = string | number;

let value: StringOrNumberUnion = 'hello, world!';
value = 100;

下面是一个更复杂的例子,编译器可以为联合类型提供一些高级保护:

type Animal = {
  name: string;
  species: string;
};

type Person = {
  name: string;
  age: number;
};

type AnimalOrPerson = Animal | Person;

const value: AnimalOrPerson = loadFromSomewhereElse();

console.log(value.name);   // 
console.log(value.age);    // 

if ('age' in value) {
  console.log(value.age); // 
}

在这个例子中,因为 Animal 和 Person 都有 name 属性,所以第 15 行的 value.name 可以正常输出,没有错误。而第 16 行的 value.age 会编译错误,因为如果 value 是 Animal 类型,则 value 是没有 age 属性的。在第 19 行的 if 块中,因为只有 value 存在 age 属性才能进入这个代码块。所以,在这个 if 块中,value 一定是 Person,TS 可以知道 value 一定是具有 age 属性的,所以编译正确。

(4)keyof 类型运算符

keyof 类型运算符返回传递给它的类型的 key 的联合。

type AppConfig = {
  username: string;
  layout: string;
};

type AppConfigKey = keyof AppConfig;

在这个例子中,AppConfigKey 类型会被解析为"username" | "layout"。它可以与索引签名一起使用:

type User = {
  name: string;
  preferences: {
    [key: string]: string;
  }
};

type UserPreferenceKey = keyof User["preferences"];

这里,UserPreferenceKey 类型被解析为 string | number

(5)元组类型

元组是一种特殊的数组类型,其中数组的元素可能是特定索引处的特定类型。它们允许 TypeScript 编译器围绕值数组提供更高的安全性,尤其是当这些值属于不同类型时。

例如,TypeScript 编译器能够为元组的各种元素提供类型安全:

type Currency = [number, string];

const amount: Currency = [100, 'USD'];

function add(values: number[]) {
   return values.reduce((a, b) => a + b);
}

add(amount);
// Error: Argument of type 'Currency' is not assignable to parameter of type 'number[]'.
// Type 'string' is not assignable to type 'number'.

上面的代码中会报错,Currency 类型的参数不能分配给“number[]”类型的参数,string 类型不能分配给 number 类型。

当访问超出元组定义类型的索引处的元素时,TypeScript 能够进行提示:

type LatLong = [number, number]; 

const loc: LatLong = [48.858370, 2.294481];

console.log(loc[2]);
// Error: Tuple type 'LatLong' of length '2' has no element at index '2'.

这里,元组类型 LatLong 只有两个元素,当试图访问第三个元素时,就会报错。

(6)条件类型

条件类型是一个表达式,类似于 JavaScript 中的三元表达式,其语法如下:

T extends U ? X : Y

来看一个实际的例子:

type ConditionalType = string extends boolean ? string : boolean;

在上面的示例中,ConditionalType 的类型将是 boolean,因为条件string extends boolean 是始终为 false。

2. 映射类型

(1)初体验

在 TypeScript 中,当需要从另一种类型派生(并保持同步)另一种类型时,使用映射类型会特别有用。

// 用户的配置值
type AppConfig = {
  username: string;
  layout: string;
};

// 用户是否有权更改配置值
type AppPermissions = {
  changeUsername: boolean;
  changeLayout: boolean;
};

在上面的代码中,AppConfig 和 AppPermissions 之间是存在隐式关系的,每当向 AppConfig 添加新的配置值时,AppPermissions 中也必须有相应的布尔值。

这里可以使用映射类型来管理两者之间的关系:

type AppConfig = {
  username: string;
  layout: string;
};

type AppPermissions = {
  [Property in keyof AppConfig as `change${Capitalize<Property>}`]: boolean
};

在上面的代码中,只要 AppConfig 中的类型发生变化,AppPermissions 就会随之变化。实现了两者之间的映射关系。

(2)概念

在 TypeScript 和 JavaScript 中,最常见的映射就是 Array.prototype.map():

[1, 2, 3].map(value => value.toString()); // ["1", "2", "3"]

这里,我们将数组中的数字映射到其字符串的表示形式。因此,TypeScript 中的映射类型意味着将一种类型转换为另一种类型,方法就是对其每个属性进行转换。

(3)实例

下面来通过一个例子来深入理解一下映射类型。对设备定义以下类型,其包含制造商和价格属性:

type Device = {
  manufacturer: string;
  price: number;
};

为了让用户更容易理解设备信息,因此为对象添加一个新类型,该对象可以使用适当的格式来格式化设备的每个属性:

type DeviceFormatter = {
  [Key in keyof Device as `format${Capitalize<Key>}`]: (value: Device[Key]) => string;
};

我们来拆解一下上面的代码。Key in keyof Device 使用 keyof 类型运算符生成 Device 中所有键的并集。将它放在索引签名中实际上是遍历 Device 的所有属性并将它们映射到 DeviceFormatter 的属性。

format${Capitalize<Key>} 是映射的转换部分,它使用 key 重映射和模板文字类型将属性名称从 x 更改为 formatX。

(value: Device[Key]) => string; 利用索引访问类型 Device[Key] 来指示格式化函数的 value 参数是格式化的属性的类型。因此,formatManufacturer 接受一个 string(制造商),而 formatPrice 接受一个number(价格)。

下面是 DeviceFormatter 类型的样子:

type DeviceFormatter = {
  formatManufacturer: (value: string) => string;
  formatPrice: (value: number) => string;
};

现在,假设将第三个属性 releaseYear 添加到 Device 类型中:

type Device = {
  manufacturer: string;
  price: number;
  releaseYear: number;
}

由于映射类型的强大功能,DeviceFormatter 类型会自动扩展为如下类型,无需进行任何额外的工作:

type DeviceFormatter = {
  formatManufacturer: (value: string) => string;
  formatPrice: (value: number) => string;
  formatReleaseYear: (value: number) => string;
};

3. 实用程序中的映射

TypeScript 附带了许多用作实用程序的映射类型,最常见的包括 Omit、Partial、Readonly、Readonly、Exclude、Extract、NonNullable、ReturnType 等。下面来看看其中的两个是如何构建的。

(1)Partial

Partial 是一种映射类型,可以将已有的类型属性转换为可选类型,并通过使用与 undefined 的联合使类型可以为空。

interface Point3D {
    x: number;
    y: number;
    z: number;
}

type PartialPoint3D = Partial<Point3D>;

这里的 PartialPoint3D 类型实际是这样的:

type PartialPoint3D = {
    x?: number;
    y?: number;
    z?: number;
}

当我们鼠标悬浮在 Partial 上时,就会看到它的定义:

把它拿出来:

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

下面来拆解一下这行代码:

  • 使用泛型来传递目标接口 T;
  • 使用 keyof T 来获取 T 的所有 key。
  • 通过使用 [P in keyof T] 来访问并循环所有的 key;
  • 它通过添加 ? 使 key 成为可选的。
  • 使用联合类型 T[P] | undefined 使 key 的类型可以为空;

(2)Exclude

Exclude 是一种映射类型,可让有选择地从类型中删除属性。其定义如下:

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

它通过使用条件类型从 T 中排除那些可分配给 U 的类型,并且在排除的属性上返回 nerver。

type animals = 'bird' | 'cat' | 'crocodile';

type mamals = Exclude<animals, 'crocodile'>;  // 'bird' | 'cat'

4. 构建映射类型

通过上面的对 TypeScript 内置实用程序类型的原理解释,对映射类型有了更深的理解。最后,我们来构建一个自己的映射类型:Optional,它可以将原类型中指定 key 的类型置为可选的并且可以为空。

我们可以这样做:

  • 将整个类型转换为 Optional
  • 从该新类型中仅选择想要的属性使其成为可选的。
  • 将原始类型与排除的属性连接起来。

实现代码及测试用例如下:

type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;

type Person = {
  name: string;
  surname: string;
  email: string;
}
  
type User = Optional<Person, 'email'>;
// 现在 email 属性是可选的

type AnonymousUser = Optional<Person, 'name' | 'surname'>;
// 现在 email 和 surname 属性是可选的

注意,这里使用 K extends keyof T 来确保只能传递属于类型/接口的属性。否则,TypeScript 将在编译时抛出错误。

映射类型的一大优点就是它们的可组合性:可以组合它们来创建新的映射类型。

上面使用了已有的实用程序类型实现了我们想要的 Optional。当然,我们也可以在不使用任何其他映射类型的情况下重新创建 Optional 映射类型实用程序:

type Optional<T, K extends keyof T> =
    { [P in K]?: T[P] }
    &
    { [P in Exclude<keyof T, K>]: T[P] };

上面的代码结合了两种类型:

  • 第一种类型通过使用 ? 修饰符使 T 的所有 K 的 key 都是可选的。
  • 第二种类型通过使用 Excluse<keyof T,K>来获取剩余的key。

以上就是深入了解TypeScript中的映射类型的详细内容,更多关于TypeScript映射类型的资料请关注我们其它相关文章!

(0)

相关推荐

  • 直观详细的typescript隐式类型转换图文详解

    正文 1.unknown是所有类型的父类型,其他类型都可以赋值给 unknown let a: undefined = undefined; let b: null = null; let x2: unknown; x2 = a; //正确 x2 = b; //正确 2.never 是任何类型的子类型,可以赋给任何类型 let a: undefined = undefined; let b: null = null; function err(): never { // OK throw new

  • TypeScript 的条件类型使用示例详解

    目录 TypeScript 的条件类型使用方式 条件类型和 keyof 组合 在条件返回中使用 T 在类型输出中使用 T 时的联合类型 使用条件类型推断类型 总结 TypeScript 的条件类型使用方式 我们可以使用 TypeScript 中的条件类型来根据逻辑定义某些类型,就像是在编写代码那样. 它采用的语法和我们在 JavaScript 中熟悉的三元运算符很像:condition ? ifConditionTrue : ifConditionFalse. 我们来看看他是怎么工作的. 假设我

  • typescript返回值类型和参数类型的具体使用

    目录 返回值类型 可缺省和可推断的返回值类型 Generator 函数的返回值 参数类型 可选参数和默认参数 剩余参数 返回值类型 在 JavaScript 中,我们知道一个函数可以没有显式 return,此时函数的返回值应该是 undefined: function fn() { // TODO } console.log(fn()); // => undefined 需要注意的是,在 TypeScript 中,如果我们显式声明函数的返回值类型为 undfined,将会得到如下所示的错误提醒.

  • 详解TypeScript使用及类型声明文件

    目录 简介 Script 与 Vue3 defineProps 与 Typescript defineEmits 与 Typescript ref 与 Typescript computed 与 Typescript 事件对象 与 Typescript 模板 Ref 与 Typescript 可选链操作符 非空断言-TS TypeScript类型声明文件 基本介绍 内置类型声明文件 第三方库类型声明文件 自定义类型声明文件 简介 声明文件是以.d.ts为后缀的文件,开发者在声明文件中编写类型声明

  • TypeScript中条件类型精读与实践记录

    目录 在泛型类型中使用条件类型 工具类型 逃离舱 在箭头函数中使用条件类型 结合类型推导使用条件类型 使用条件类型来判断两个类型完全相等 总结 在大多数程序中,我们必须根据输入做出决策.TypeScript 也不例外,使用条件类型可以描述输入类型与输出类型之间的关系. 用于条件判断时的 extends 当 extends 用于表示条件判断时,可以总结出以下规律 若位于 extends 两侧的类型相同,则 extends 在语义上可理解为 ===,可以参考如下例子: type result1 =

  • Typescript类型系统FLOW静态检查基本规范

    目录 类型系统 强类型和弱类型(类型安全) 静态类型与动态类型(类型检查) JavaScript自由类型系统的问题 Flow静态类型检查方案 Typescript语言规范与基本应用 Typescript作用域 Typescript原始类型 Typescript Object类型 Typescript数组类型 Typescript元组类型(turple) Typescript枚举类型(enum) TypeScript函数类型 TypeScript任意类型 隐式类型判断 TypeScript类型断言

  • 深入了解TypeScript中的映射类型

    目录 1. 基本概念 (1)索引访问类型 (2)索引签名 (3)联合类型 (4)keyof 类型运算符 (5)元组类型 (6)条件类型 2. 映射类型 (1)初体验 (2)概念 (3)实例 3. 实用程序中的映射 (1)Partial (2)Exclude 4. 构建映射类型 DRY 原则(Don't repeat yourself)是软件开发中最重要的原则之一,即不要重复自己.应该避免在代码中的两个或多个地方存在重复的业务逻辑. 在 TypeScript 中,映射类型可以帮助我们避免编写重复的

  • Python中字典映射类型的学习教程

    字典是python语言中唯一的映射类型,用花括号{}表示,一个字典条目就是一个键值对,方法keys()返回字典的键列表,values()返回字典的值列表,items()返回字典的键值对列表.字典中的值没有任何限制,它们可以是任意python对象,但字典中的键是有类型限制的,每个键只能对应一个值,且键必须是可哈系的,所有不可变类型都是可哈希的.不可变集合frozenset的元素可作为字典的键,但可变集合set就不行了. 以下是字典类型的常用方法. clear():删除字典中所有元素. copy()

  • TypeScript中的交叉类型和联合类型示例讲解

    目录 交叉类型(Intersection types) 要点 联合类型(Union types) 类型缩减 交叉类型(Intersection types) 什么事交叉类型呢?简单来说就是通过&符号将多个类型进行合并成一个类型,然后用type来声明新生成的类型.这里我举个例子,具体如下: interface ClassA{ name:string; age:number } interface ClassB{ name:string; phone:number; } 将接口ClassA和接口Cl

  • 详解TypeScript中的类型保护

    概述 在 TypeScript 中使用联合类型时,往往会碰到这种尴尬的情况: interface Bird { // 独有方法 fly(); // 共有方法 layEggs(); } interface Fish { // 独有方法 swim(); // 共有方法 layEggs(); } function getSmallPet(): Fish | Bird { // ... } let pet = getSmallPet(); pet.layEggs(); // 正常 pet.swim();

  • 在 TypeScript 中使用泛型的方法

    目录 1. 泛型语法 2. 在函数中使用泛型 (1)分配泛型参数 (2)直接传递类型参数 (3)默认类型参数 (4)类型参数约束 3. 在接口.类和类型中使用泛型 (1)接口和类中的泛型 (2)自定义类型中的泛型 4. 使用泛型创建映射类型 5. 使用泛型创建条件类型 (1)基础条件类型 (2)高级条件类型 6. 小结 前言: 泛型是静态类型语言的基本特征,允许将类型作为参数传递给另一个类型.函数.或者其他结构.TypeScript 支持泛型作为将类型安全引入组件的一种方式.这些组件接受参数和返

  • Elasticsearch的删除映射类型操作示例

    目录 一 前言 二 什么是映射类型? 三 为什么要删除映射类型? 四 映射类型的替代方法 4.1 将映射类型分开存储在索引中 4.2 自定义类型字段回到顶部 五 没有映射类型的父/子 六 删除映射类型的计划 七将多类型索引迁移到单一类型 7.1 每种文档类型的索引 7.2 自定义类型字段 八 总结 一 前言 官方解释:https://www.elastic.co/guide/en/elasticsearch/reference/6.0/removal-of-types.html 在elastic

  • 详解TypeScript映射类型和更好的字面量类型推断

    概述 TypeScript 2.1 引入了映射类型,这是对类型系统的一个强大的补充.本质上,映射类型允许w咱们通过映射属性类型从现有类型创建新类型.根据咱们指定的规则转换现有类型的每个属性.转换后的属性组成新的类型. 使用映射类型,可以捕获类型系统中类似Object.freeze()等方法的效果.冻结对象后,就不能再添加.更改或删除其中的属性.来看看如何在不使用映射类型的情况下在类型系统中对其进行编码: interface Point { x: number; y: number; } inte

  • TypeScript中枚举类型的理解与应用场景

    目录 一.是什么 二.使用 数字枚举 字符串枚举 异构枚举 本质 三.应用场景 总结 一.是什么 枚举是一个被命名的整型常数的集合,用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型 通俗来说,枚举就是一个对象的所有可能取值的集合 在日常生活中也很常见,例如表示星期的SUNDAY.MONDAY.TUESDAY.WEDNESDAY.THURSDAY.FRIDAY.SATURDAY就可以看成是一个枚举 枚举的说明与结构和联合相似,其形式为: enum 枚举名{     标识

  • TypeScript 映射类型详情

    目录 1.映射类型(Mapped Types) 2.映射修饰符(Mapping Modifiers) 3.通过 as 实现键名重新映射(Key Remapping via as) 4.深入探索(Further Exploration) 前言: TypeScript 的官方文档早已更新,但我能找到的中文文档都还停留在比较老的版本.所以对其中新增以及修订较多的一些章节进行了翻译整理. 本篇翻译整理自 TypeScript Handbook 中 「Mapped Types」 章节. 本文并不严格按照原

  • 详解Python中映射类型的内建函数和工厂函数

    1.基本函数介绍 (1)标准类型函数[type().str()和 cmp()]         对一个字典调用type()工厂方法,会返回字典类型:"<type 'dict'>".调用str()工厂方法将返回该字典的字符串表示形式.         字典是通过这样的算法来比较的:首先是字典的大小,然后是键,最后是值.可是用cmp()做字典的比较一般不是很有用. 算法按照以下的顺序: 首先比较字典长度         如果字典的长度不同,那么用cmp(dict1, dict2

随机推荐