阮一峰《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(该属性被过滤)。

相关推荐
jonjia12 小时前
模块、脚本与声明文件
typescript
jonjia12 小时前
配置 TypeScript
typescript
jonjia12 小时前
TypeScript 工具函数开发
typescript
jonjia12 小时前
注解与断言
typescript
jonjia12 小时前
IDE 超能力
typescript
jonjia12 小时前
对象类型
typescript
jonjia12 小时前
快速搭建 TypeScript 开发环境
typescript
jonjia12 小时前
TypeScript 的奇怪之处
typescript
jonjia12 小时前
类型派生
typescript
jonjia12 小时前
开发流程中的 TypeScript
typescript