阮一峰《TypeScript 教程》学习笔记——类型映射

1. 一段话总结

TypeScript 的类型映射 是将一种类型按规则转换为另一种类型的核心机制,主要用于对象类型,核心语法为 [prop in keyof Type]: TargetType(通过keyof提取原类型键名,in遍历键名联合类型);支持通过映射修饰符+?/-?控制可选属性、+readonly/-readonly控制只读属性)调整属性的可选/只读特性;TypeScript 4.1 引入的键名重映射as子句,如[p in keyof A as ${p}ID])可修改键名,还能实现属性过滤(通过条件类型返回never排除属性);此外,类型映射可封装为泛型提高复用性,且与Partial<T>/Readonly<T>/Required<T>等内置工具类型直接关联,广泛用于属性类型批量修改、特性调整及键名优化。


2. 思维导图


3. 详细总结

一、类型映射简介

TypeScript 类型映射的核心是"按预设规则将源类型转换为目标类型",主要针对对象类型,解决批量处理属性类型或特性的问题,避免重复编写相似类型。

1. 基础语法与核心逻辑

语法组成 作用 示例
prop 属性名变量,名称可自定义(如PKey -
in 遍历运算符,逐个取出右侧联合类型的成员 P in keyof A(遍历A的键名)
keyof Type 提取源类型Type的所有键名,生成联合类型(如keyof {a:number} → 'a' `keyof {foo:number; bar:string} → 'foo'
TargetType 目标属性类型,可固定(如string)或关联源类型(如Type[P] [P in keyof A]: A[P](复制源类型)

示例1:基础类型转换

将源类型A的所有属性转为string类型:

typescript 复制代码
type A = { foo: number; bar: number };
type B = { [P in keyof A]: string }; // { foo: string; bar: string }

示例2:复制源类型

通过Type[P]关联源属性类型,实现类型复制:

typescript 复制代码
type A = { foo: number; bar: string };
type B = { [P in keyof A]: A[P] }; // 与A完全一致

2. 泛型封装(提高复用性)

将映射逻辑封装为泛型,可复用至任意对象类型:

typescript 复制代码
// 泛型:将任意类型的所有属性转为boolean
type ToBoolean<Type> = { [P in keyof Type]: boolean };
// 用法:将User的所有属性转为boolean
type User = { name: string; age: number };
type UserBoolean = ToBoolean<User>; // { name: boolean; age: boolean }

二、映射修饰符

映射会默认保留源类型的"可选(?)"和"只读(readonly)"特性,若需调整,需使用映射修饰符+添加特性,-移除特性)。

1. 修饰符分类与用法

修饰符类型 语法 作用 示例(基于type A = { a: string; b: number }
添加可选属性 +?(简写为? 为所有属性添加?,转为可选属性 type OptionalA = { [P in keyof A]?: A[P] } → { a?: string; b?: number }
移除可选属性 -? 移除所有属性的?,转为必选属性(对应内置Required<T> type ConcreteA = { [P in keyof A]-?: A[P] } → { a: string; b: number }
添加只读属性 +readonly(简写为readonly 为所有属性添加readonly,转为只读属性(对应内置Readonly<T> type ReadonlyA = { readonly [P in keyof A]: A[P] } → { readonly a: string; readonly b: number }
移除只读属性 -readonly 移除所有属性的readonly,转为可写属性 type MutableA = { -readonly [P in keyof A]: A[P] } → { a: string; b: number }

2. 组合使用修饰符

同时调整"只读"和"可选"特性:

typescript 复制代码
// 为所有属性添加readonly和可选
type ReadonlyOptional<T> = { +readonly [P in keyof T]+?: T[P] };
// 移除所有属性的readonly和可选
type MutableConcrete<T> = { -readonly [P in keyof T]-?: T[P] };

三、键名重映射(TypeScript 4.1+)

键名重映射通过as子句修改源类型的键名,支持模板字符串、条件类型等,还可实现属性过滤。

1. 核心语法

typescript 复制代码
type 目标类型 = {
  [P in keyof 源类型 as 新键名类型]: 目标属性类型;
};
  • 新键名类型 :常用模板字符串(如${P}ID)、条件类型(如T[P] extends string ? P : never)。

2. 常见场景示例

场景 代码示例 结果类型
键名添加后缀 type A = { foo: number; bar: number }; type B = { [P in keyof A as ${P}ID]: number } { fooID: number; barID: number }
键名添加前缀并大写 type Person = { name: string; age: number }; type Getters<T> = { [P in keyof T as get${Capitalize<string & P>}]: () => T[P] } { getName: ()=>string; getAge: ()=>number }
属性过滤(保留字符串属性) type User = { name: string; age: number }; type FilterString<T> = { [K in keyof T as T[K] extends string ? K : never]: string } { name: string }

3. 关键逻辑:属性过滤

通过条件类型,若属性不满足要求则返回never(表示该属性不存在),实现过滤:

typescript 复制代码
type FilterNumber<T> = {
  // 仅保留属性值为number的属性,否则键名转为never(过滤)
  [K in keyof T as T[K] extends number ? K : never]: number;
};
type Data = { a: number; b: string; c: number };
type NumberData = FilterNumber<Data>; // { a: number; c: number }

四、联合类型的映射

键名重映射支持基于"非键名联合类型"(如对象联合类型)生成新类型,核心是从联合类型成员中提取字段作为新键名。

示例 :基于含kind字段的对象联合类型生成函数类型对象

typescript 复制代码
// 源联合类型:含kind字段的对象类型
type Square = { kind: 'square'; x: number; y: number };
type Circle = { kind: 'circle'; radius: number };

// 映射逻辑:以kind字段值为键名,生成函数类型
type EventHandlers<Events extends { kind: string }> = {
  [E in Events as E['kind']]: (event: E) => void;
};

// 生成目标类型
type ShapeHandlers = EventHandlers<Square | Circle>;
// 结果:{ square: (event:Square)=>void; circle: (event:Circle)=>void }

五、与内置工具类型的关联

TypeScript 内置工具类型多基于类型映射实现,核心对应关系如下:

内置工具类型 实现逻辑(简化版) 作用
Partial<T> { [P in keyof T]?: T[P] } 将所有属性转为可选
Readonly<T> { readonly [P in keyof T]: T[P] } 将所有属性转为只读
Required<T> { [P in keyof T]-?: T[P] } 移除所有属性的可选特性,转为必选

4. 关键问题

问题1:TypeScript 类型映射的基础语法是什么?其核心作用的解决了什么开发痛点?

答案

  • 基础语法 :类型映射的核心语法为 [prop in keyof Type]: TargetType,其中:

    • keyof Type:提取源类型Type的所有键名,生成联合类型;
    • in:遍历该联合类型,逐个获取键名并赋值给变量prop
    • TargetType:指定映射后属性的目标类型(可固定或关联源类型,如Type[prop])。
  • 解决的痛点
    开发中若需批量处理对象类型的属性(如将所有属性转为boolean、统一添加readonly),手动编写重复类型定义会导致冗余且易出错;类型映射通过"规则化转换"实现批量处理,减少重复代码,同时提高类型定义的复用性(如封装为泛型后可复用至任意对象类型)。

  • 示例
    批量将User类型的所有属性转为string

    typescript 复制代码
    type User = { name: string; age: number };
    type UserString = { [P in keyof User]: string }; // { name: string; age: string }

问题2:映射修饰符有哪几类?分别适用于哪些场景?请结合示例说明。

答案

映射修饰符分为可选属性修饰符只读属性修饰符,核心用于调整属性的"可选"或"只读"特性,共4种常用类型:

修饰符类别 具体修饰符 语法示例 适用场景
可选属性修饰符 添加可选 [P in keyof T]+?: T[P](简写? 需将对象所有属性转为可选(如表单初始值类型)
可选属性修饰符 移除可选 [P in keyof T]-?: T[P] 需将对象所有可选属性转为必选(如校验后的数据类型)
只读属性修饰符 添加只读 +readonly [P in keyof T]: T[P](简写readonly 需禁止修改对象属性(如配置项类型)
只读属性修饰符 移除只读 -readonly [P in keyof T]: T[P] 需解除属性的只读限制(如动态更新的数据类型)

示例1:添加可选属性

Config类型的所有属性转为可选,用于表单初始值:

typescript 复制代码
type Config = { theme: string; fontSize: number };
type OptionalConfig = { [P in keyof Config]?: Config[P] };
const initConfig: OptionalConfig = { theme: 'light' }; // 允许省略fontSize

示例2:移除可选属性

OptionalConfig的可选属性转为必选,用于校验后的确定数据:

typescript 复制代码
type RequiredConfig = { [P in keyof OptionalConfig]-?: OptionalConfig[P] };
const finalConfig: RequiredConfig = { theme: 'light', fontSize: 16 }; // 必须包含所有属性

问题3:键名重映射的语法是什么?如何通过它实现"属性过滤"?请结合示例说明原理。

答案

  • 键名重映射语法 :TypeScript 4.1+ 支持通过as子句修改键名,语法为:

    typescript 复制代码
    type 目标类型 = { [P in keyof 源类型 as 新键名类型]: 目标属性类型 };

    其中"新键名类型"可通过模板字符串(如${P}ID)、条件类型等生成,核心是通过as子句重新定义键名。

  • 属性过滤原理

    利用"条件类型返回never表示属性不存在"的特性,在as子句中添加条件判断:若属性满足要求,则保留原键名;若不满足,则返回never(过滤该属性)。

  • 示例:过滤非函数类型的属性

    Action类型中仅保留"属性值为函数"的属性:

    typescript 复制代码
    // 源类型:含函数和非函数属性
    type Action = {
      add: (x: number, y: number) => number;
      name: string;
      subtract: (x: number, y: number) => number;
      age: number;
    };
    
    // 映射逻辑:仅保留函数类型属性
    type FilterFunction<T> = {
      [K in keyof T as T[K] extends (...args: any[]) => any ? K : never]: T[K];
    };
    
    // 过滤结果:仅保留add和subtract
    type FunctionAction = FilterFunction<Action>;
    // 结果类型:{ add: (x:number,y:number)=>number; subtract: (x:number,y:number)=>number }

    原理:对每个键名K,判断T[K]是否为函数类型;若是,保留键名K;若不是,返回never(该属性被过滤)。

相关推荐
月阳羊3 小时前
【论文学习与撰写】Mathtype的安装与word插件安装
学习·word
ouliten3 小时前
cuda编程笔记(34)-- 内存访问控制与缓存提示
笔记·cuda
BreezeJuvenile4 小时前
MAX30102脉搏血氧传感器相关内容整理(理论版)
学习·max30102·心率血氧传感器
报错小能手4 小时前
C++笔记(面向对象)深赋值 浅赋值
c++·笔记·学习
一介书生-0074 小时前
2025-10-27 Java AI学习路线
java·人工智能·学习
superlls4 小时前
(场景题)怎么实现数据的批量插入?
笔记·mybatis
繁花与尘埃4 小时前
CSS引入方式(本文为个人学习笔记,内容整理自哔哩哔哩UP主【非学者勿扰】的公开课程。 > 所有知识点归属原作者,仅作非商业用途分享)
css·笔记·学习
Century_Dragon5 小时前
新能源汽车故障诊断与排除虚拟实训软件:赋能职业教育利器
学习
_落纸5 小时前
《自动控制原理》第 3 章 线性控制系统的运动分析:3.6、3.7
笔记·自动化