TypeScript编程06-映射类型
TypeScript编程06-映射类型
文章目录
- TypeScript编程06-映射类型
- 第一章:基础概念
- 1.1 什么是映射类型?
- 第二章:Partial<T> - 可选化
- 2.1 基本用法
- 2.2 实现原理
- 第三章:Required<T> - 必選化
- 3.1 基本用法
- 3.2 实现原理
- 第四章:Readonly<T> - 只读化
- 4.1 基本用法
- 4.2 浅只读 vs 深只读
- 第五章:Pick<T, K> - 属性挑选
- 5.1 基本用法
- 5.2 实现原理
- 第六章:Omit<T, K> - 属性排除
- 6.1 基本用法
- 6.2 与 Pick 的关系
- 第七章:Record<K, T> - 键值映射
- 7.1 基本用法
- 7.2 与索引签名的区别
- 第八章:组合应用与实战
- 8.1 如何组合使用这些映射类型?
- 8.2 自定义映射类型
- 第九章:常见问题与陷阱
- 9.1 可选属性与 undefined 的区别
- 9.2 映射类型对方法的影响
- 9.3 keyof 与联合类型的顺序
- 第十章:速查表
第一章:基础概念
1.1 什么是映射类型?
Q:什么是 TypeScript 中的映射类型?
A:映射类型(Mapped Types)是 TypeScript 提供的基于旧类型创建新类型的工具,它会遍历目标类型的每个属性并应用特定的转换规则。本质上是一种类型层面的"批量操作"工具。
// 基础语法结构typeNewType={[KinkeyofOldType]:NewValueType}第二章:Partial<T> - 可选化
2.1 基本用法
Q:Partial<T>的作用是什么?
A:将类型T的所有属性变为可选(?)。常用于函数参数、表单更新等场景。
interfaceUser{name:string;age:number;email:string;}typePartialUser=Partial<User>;// 等价于:// {// name?: string;// age?: number;// email?: string;// }// 实际应用:更新用户信息functionupdateUser(user:User,updates:Partial<User>):User{return{...user,...updates};}constuser={name:"张三",age:25,email:"zhang@example.com"};updateUser(user,{age:26});// ✅ 只需传入想更新的字段2.2 实现原理
Q:Partial<T>是如何实现的?
A:
typePartial<T>={[PinkeyofT]?:T[P];};关键点:
keyof T:获取T的所有键组成的联合类型P in ...:遍历每个键?::将属性标记为可选
第三章:Required<T> - 必選化
3.1 基本用法
Q:Required<T>的作用是什么?
A:将类型T的所有属性变为必选(移除?)。与Partial<T>作用相反。
interfaceConfig{host?:string;port?:number;ssl?:boolean;}typeRequiredConfig=Required<Config>;// 等价于:// {// host: string;// port: number;// ssl: boolean;// }// 实际应用:确保配置完整functioncreateServer(config:RequiredConfig){// 此时 config 的所有属性都一定有值console.log(config.host,config.port,config.ssl);}3.2 实现原理
Q:Required<T>是如何实现的?
A:
typeRequired<T>={[PinkeyofT]-?:T[P];};关键点:
-?:移除可选修饰符(-表示移除,?表示可选)
第四章:Readonly<T> - 只读化
4.1 基本用法
Q:Readonly<T>的作用是什么?
A:将类型T的所有属性变为只读(readonly),防止属性被重新赋值。
interfacePoint{x:number;y:number;}typeReadonlyPoint=Readonly<Point>;// 等价于:// {// readonly x: number;// readonly y: number;// }constpoint:ReadonlyPoint={x:10,y:20};point.x=5;// ❌ 报错:无法为只读属性赋值4.2 浅只读 vs 深只读
Q:Readonly<T>是深只读吗?
A:不是,它是浅只读(Shallow Readonly),只影响第一层属性。
interfaceUser{info:{name:string;age:number};}constuser:Readonly<User>={info:{name:"张三",age:25}};// user.info = { name: "李四", age: 30 }; // ❌ 报错:不能修改 info 本身user.info.name="李四";// ✅ 不报错:可以修改嵌套对象的属性// 深只读实现typeDeepReadonly<T>={readonly[PinkeyofT]:T[P]extendsobject?DeepReadonly<T[P]>:T[P];};constdeepUser:DeepReadonly<User>={info:{name:"张三",age:25}};// deepUser.info.name = "李四"; // ❌ 报错:深层属性也是只读的第五章:Pick<T, K> - 属性挑选
5.1 基本用法
Q:Pick<T, K>的作用是什么?
A:从类型T中挑选出一组属性K来构造新类型。
interfaceUser{id:number;name:string;email:string;password:string;createdAt:Date;}// 只选择 id 和 nametypeUserPreview=Pick<User,"id"|"name">;// 等价于:// {// id: number;// name: string;// }// 实际应用:API 返回精简数据functiongetUserPreview(id:number):Promise<UserPreview>{returnfetch(`/api/users/${id}/preview`).then(r=>r.json());}5.2 实现原理
Q:Pick<T, K>是如何实现的?
A:
typePick<T,KextendskeyofT>={[PinK]:T[P];};关键点:
K extends keyof T:约束K必须是T的键的子集- 遍历
K而非keyof T,实现选择性提取
第六章:Omit<T, K> - 属性排除
6.1 基本用法
Q:Omit<T, K>的作用是什么?
A:从类型T中排除一组属性K来构造新类型。与Pick<T, K>相反。
interfaceUser{id:number;name:string;email:string;password:string;createdAt:Date;}// 排除敏感字段 password 和 createdAttypePublicUser=Omit<User,"password"|"createdAt">;// 等价于:// {// id: number;// name: string;// email: string;// }// 实际应用:API 返回公开数据functionsanitizeUser(user:User):PublicUser{const{password,createdAt,...publicInfo}=user;returnpublicInfo;}6.2 与 Pick 的关系
Q:Omit和Pick可以互相转换吗?
A:可以,它们是互补的:
// Omit 可以用 Pick + Exclude 实现typeOmit<T,Kextendskeyofany>=Pick<T,Exclude<keyofT,K>>;// 示例验证typeA=Omit<User,"password">;typeB=Pick<User,"id"|"name"|"email"|"createdAt">;// A 和 B 是等价的第七章:Record<K, T> - 键值映射
7.1 基本用法
Q:Record<K, T>的作用是什么?
A:构造一个对象类型,其键的类型为K,值的类型为T。常用于创建字典、映射表。
// 键为字符串,值为数字typePageNames="home"|"about"|"contact";typePageInfo=Record<PageNames,{title:string;path:string}>;constpages:PageInfo={home:{title:"首页",path:"/"},about:{title:"关于",path:"/about"},contact:{title:"联系",path:"/contact"}// 不能添加其他键,也不能缺少任何键};7.2 与索引签名的区别
Q:Record<K, T>和索引签名有什么区别?
A:
| 特性 | Record<K, T> | 索引签名 |
|---|---|---|
| 键的范围 | 有限的联合类型 | 无限的类型(如string) |
| 必须包含所有键 | ✅ 是 | ❌ 否 |
| 适用场景 | 固定键集合 | 动态键集合 |
// Record:键是有限的typeStatus="pending"|"success"|"error";conststatusMap:Record<Status,string>={pending:"处理中",success:"成功",error:"失败"// 不能少也不能多};// 索引签名:键是无限的interfaceDictionary{[key:string]:number;}constdict:Dictionary={a:1,b:2,// 可以有任意多个键};第八章:组合应用与实战
8.1 如何组合使用这些映射类型?
Q:实际开发中如何组合使用这些工具类型?
A:可以链式组合,解决复杂类型问题:
interfaceProduct{id:number;name:string;price:number;stock:number;description:string;createdAt:Date;updatedAt:Date;}// 场景:创建产品时,只需要部分字段,且都是可选的typeCreateProductDTO=Partial<Pick<Product,"name"|"price"|"description"|"stock">>;// 场景:更新产品时,id 必选,其他可选typeUpdateProductDTO=Partial<Omit<Product,"id"|"createdAt"|"updatedAt">>&Pick<Product,"id">;// 场景:前端展示用的产品信息(排除敏感字段,且只读)typeProductView=Readonly<Omit<Product,"stock"|"updatedAt">>;// 场景:按状态分组的产品列表typeProductStatus="available"|"outOfStock"|"discontinued";typeProductsByStatus=Record<ProductStatus,Product[]>;8.2 自定义映射类型
Q:如何基于这些工具类型创建自定义映射类型?
A:可以扩展或组合内置工具类型:
// 1. 可空类型(值可以是 null)typeNullable<T>={[PinkeyofT]:T[P]|null;};// 2. 可选且只读typeReadonlyPartial<T>=Readonly<Partial<T>>;// 3. 深部分(递归处理嵌套对象)typeDeepPartial<T>={[PinkeyofT]?:T[P]extendsobject?DeepPartial<T[P]>:T[P];};// 4. 提取函数参数类型typeParameters<Textends(...args:any[])=>any>=Textends(...args:inferP)=>any?P:never;// 5. 提取函数返回值类型typeReturnType<Textends(...args:any[])=>any>=Textends(...args:any[])=>inferR?R:any;第九章:常见问题与陷阱
9.1 可选属性与 undefined 的区别
Q:Partial<T>和将属性设为undefined有什么区别?
A:
interfaceA{x?:string;// 可选属性:可以不存在y:string|undefined;// 必选但可为 undefined:必须存在,值可以是 undefined}consta1:A={};// ❌ 缺少 yconsta2:A={y:undefined};// ✅ y 必须存在,即使值是 undefined9.2 映射类型对方法的影响
Q:Readonly<T>会影响类的方法吗?
A:会,但不会阻止方法内部修改状态:
classCounter{count=0;increment(){this.count++;// 内部可以修改}}constreadonlyCounter:Readonly<Counter>=newCounter();// readonlyCounter.count = 5; // ❌ 报错readonlyCounter.increment();// ✅ 允许调用,内部修改了 count9.3 keyof 与联合类型的顺序
Q:Pick和Omit的第二个参数顺序重要吗?
A:不重要,联合类型是无序的:
typeA=Pick<User,"id"|"name">;typeB=Pick<User,"name"|"id">;// A 和 B 完全相同第十章:速查表
| 工具类型 | 作用 | 记忆口诀 |
|---|---|---|
Partial<T> | 所有属性可选 | Partial =Partial(部分的) |
Required<T> | 所有属性必选 | Required =Required(必需的) |
Readonly<T> | 所有属性只读 | Readonly =Read(只读) |
Pick<T, K> | 挑选指定属性 | Pick =Pick(挑选) |
Omit<T, K> | 排除指定属性 | Omit =Omit(省略) |
Record<K, T> | 创建键值映射 | Record =Record(记录/字典) |
