当前位置: 首页 > news >正文

Typescript高级: 深入实践Record类型

概述

  • Record 类型是TS中其众多强大特性之一
  • 它为我们提供了创建键值对映射的强大能力
  • 极大地增强了代码的灵活性与类型安全性

应用示例


1 )用于配置场景

  • 在复杂的项目中,配置文件往往包含多个模块的不同设置
  • 使用 Record 可以确保配置的键名正确且值类型统一

示例

interface AppConfig {port: number;env: 'development' | 'production';
}const config: Record<string, AppConfig> = {server: { port: 3000, env: 'development' },client: { port: 8080, env: 'production' },
};

2 )枚举场景

  • Record 也常用于将枚举类型转换为易于使用的键值对映射,便于在代码中引用

示例

enum Color {Red,Green,Blue,
}const colorNameMap: Record<Color, string> = {[Color.Red]: 'Red',[Color.Green]: 'Green',[Color.Blue]: 'Blue',
};

3 )联合类型场景

示例

type Scores = Record<'Math' | 'English' | 'Science', number>;  const studentScores: Scores = {  Math: 90,  English: 85,  Science: 92  
};

4 )表单项的配置

  • 在构建动态表单时,Record 可以用来定义表单项的配置,每个表单项都有其特有的属性

示例

interface FormField {type: 'text' | 'checkbox' | 'select';label: string;required?: boolean;
}const formConfig: Record<string, FormField> = {username: { type: 'text', label: 'Username', required: true },agreeTerms: { type: 'checkbox', label: 'Agree to terms', required: false },
};

5 ) 映射类型类似Record

type Invert<T> = {  [P in keyof T as T[P] extends string ? T[P] : never]: P;  
};  type Original = { a: '1'; b: '2' };  
type Inverted = Invert<Original>;  // 测试反转类型  
const invertedObj: Inverted = {  '1': 'a',  '2': 'b'  
};  // 这是一个类型守卫函数,用于在运行时检查对象是否符合 Inverted 类型  
function isInvertedType(obj: any): obj is Inverted {  return '1' in obj && '2' in obj && obj['1'] === 'a' && obj['2'] === 'b';  
}  if (isInvertedType(invertedObj)) {  console.log('invertedObj 符合 Inverted 类型');  
} else {  console.log('invertedObj 不符合 Inverted 类型');  
}
  • Invert<T> 类型别名通过条件映射
  • 使用了as关键字的映射类型语法,这在TypeScript 4.1及以后版本中可用
  • 来生成一个新的类型,该类型实质上是原类型T中每个键值对的倒置
  • 但这种方式更加灵活,因为它基于值的类型来进行映射
  • 在这个例子中,要求原对象的值必须是字符串,从而可以用作新类型的键
  • 因此,虽然Inverted类型在功能上类似于某种Record类型的应用
  • 但它更准确地描述为一个经过类型系统转换处理后得到的、具有特定键值对的对象类型,而不是直接等同于使用Record定义的类型

6 )自定义Record(硬编码联合类型映射)

示例

// 1. 使用 TypeScript 内置的 Record 类型  
type UserRecord = Record<string, any>; // 允许任何字符串作为键,任何类型作为值  // 假设我们有一个具体的键集合和值类型  
type SpecificKeys = 'username' | 'age';  
type SpecificUserRecord = Record<SpecificKeys, string>; // 键必须是 'username' 或 'age',值必须是 string  // 2. 自定义一个类似 Record 的类型,但键是硬编码的(这不是一个好的做法,因为它不够灵活)  
type MyRecord<T> = {  [P in 'username' | 'age']: T; // 硬编码的键 'username' 和 'age',值类型由泛型 T 指定  
};// 使用自定义的 MyRecord 类型
type MyUserRecord = MyRecord<string>; // 键是 'username' 或 'age',值都是 string  // 示例对象  
const user: SpecificUserRecord = {  username: 'Alice',  age: '30' // 注意:这里为了演示使用了字符串,但在实际应用中年龄应该是数字  
};  const myUser: MyUserRecord = {  username: 'Bob',  age: '25' // 同样,这里使用了字符串作为示例  
};  // 说明:  
// 1. 内置的 Record 类型更加灵活,允许您指定任何键的集合(K extends keyof any)和值的类型(T)。  
// 2. 自定义的 MyRecord 类型在这里是硬编码的,它只允许 'username' 和 'age' 作为键,并且值的类型由泛型 T 指定。  
// 3. 在实际应用中,通常推荐使用内置的 Record 类型,因为它更加灵活且易于维护。

7 ) 类型谓词与模式匹配

示例

function isRecordOfStrings(obj: any): obj is Record<string, string> {return typeof obj === 'object' && obj !== null && Object.values(obj).every(v => typeof v === 'string');
}if (isRecordOfStrings(someObj)) {// 在此块内,someObj 被认为是 Record<string, string>
}
  • 通过类型谓词和 Record 结合,可以实现更加精准的类型判断和模式匹配逻辑

8 ) Record 与 索引签名

示例

// 假设我们有一个Goods类型  
type Goods = {  id: number;  name: string;  
};  // TypeScript内置的Record类型  
type RecordType<K extends keyof any, T> = {  [P in K]: T;  
};  // 使用Record类型来创建一个新的类型,该类型的属性与Goods的键相同,但值都是string类型  
type resultGoodsType = RecordType<keyof Goods, string>;  
// 这等价于:  
// type resultGoodsType = {  
//   id: string;  
//   name: string;  
// };  // 类似索引签名的类型定义(但不是Record)  
type IndexSignatureType = {  [x: string]: any; // 字符串索引签名,允许任何字符串作为键,值是any类型  // 注意:一旦你定义了字符串索引签名,那么就不能再为特定的属性名定义更具体的类型了  // 因为所有属性名最终都会被视为字符串,并遵循这个索引签名的类型  
};  // 索引签名不能包含number或symbol作为键类型(除了特殊的数组和Map类型)  
// type BadIndexSignatureType = {  
//   [x: number]: any; // 错误:索引签名参数类型必须为 "string" 或 "number" 或 "symbol"。  
// };  // 使用Record和索引签名的区别  
// Record类型允许你明确指定键的类型和值的类型  
// 而索引签名则提供了一种更通用的方式来描述对象的结构,但会限制你对特定键的类型定义  // 示例:使用Record和索引签名  
const recordExample: resultGoodsType = {  id: '123', // 符合resultGoodsType的定义,因为id现在是string类型  name: 'Apple' // 符合resultGoodsType的定义,因为name现在是string类型  
};  const indexSignatureExample: IndexSignatureType = {  id: 123, // 符合IndexSignatureType的定义,因为id被视为字符串键,但其值可以是any类型  name: 'Banana', // 符合IndexSignatureType的定义  'some-other-key': true // 也符合,因为所有字符串键都符合索引签名的定义  
};

联系:

  • Record类型和索引签名都用于描述对象的结构
  • 它们都允许你使用类型参数来定义键和值的类型

区别:

  • Record是一个内置工具类型,它接受两个类型参数来明确指定键和值的类型
  • 索引签名是TypeScript中对象类型定义的一部分,它允许你定义一个或多个类型的键(通常是string或number),并为这些键指定一个值的类型
  • 一旦你定义了一个字符串索引签名,那么你就不能再为对象的特定属性定义更具体的类型了(除非该类型与索引签名的类型兼容)
  • 这是因为所有属性名最终都会被视为字符串,并遵循这个索引签名的类型。
  • Record类型提供了一种更精确和可控的方式来定义对象的结构,而索引签名则提供了一种更通用和灵活的方式来描述对象的结构

9 )实现轻量级 Map

type SimpleMap<KeyType extends string | number | symbol, ValueType> = {[key in KeyType]: ValueType;
};class LightMap<KeyType extends string | number | symbol, ValueType> {private map: SimpleMap<KeyType, ValueType>;constructor() {this.map = {} as SimpleMap<KeyType, ValueType>;}set(key: KeyType, value: ValueType): void {this.map[key] = value;}get(key: KeyType): ValueType | undefined {return this.map[key];}delete(key: KeyType): boolean {const hasKey = key in this.map;if (hasKey) {delete this.map[key];}return hasKey;}has(key: KeyType): boolean {return key in this.map;}
}const myMap = new LightMap<string, number>();myMap.set('apple', 1);
myMap.set('banana', 2);console.log(myMap.get('apple')); // 输出:1
console.log(myMap.has('cherry')); // 输出:false
console.log(myMap.delete('banana')); // 输出:true
console.log(myMap.has('banana')); // 输出:false
  • Record是TypeScript的一个内置类型别名,它允许你基于一个索引类型(键)和一个值类型创建一个对象类型
  • 例如,Record<string, number>定义了一个所有键为字符串、值为数字的对象类型
  • 我们的轻量级Map实现将包括以下几个基本操作:
    • 设置值: set(key, value)
    • 获取值:get(key)
    • 删除值:delete(key)
    • 检查找键是否存在:has(key)
  • 泛型SimpleMap,允许键为string、number或symbol,值为任意类型ValueType
  • 通过TypeScript的Record类型和类,我们实现了一个轻量级的Map数据结构,它在编译时提供类型安全检查,确保了键值的类型正确性
  • 虽然它不具备JavaScript内置Map对象的所有功能(如迭代器、容量自动扩展等),但足以应对一些简单场景,且在类型安全方面提供了额外保障
http://www.lryc.cn/news/361608.html

相关文章:

  • 重构与优化-对象间特性搬移重构(2)
  • 网络流量监控与DNS流量分析
  • 【数据分析】打造完美数据分析环境:Python开发环境搭建全攻略
  • 我的app开始养活我了
  • linux中最基础使用的命令
  • 【算法实战】每日一题:17.1 订单处理问题(差分思想,二分搜索)
  • UML静态图-对象图
  • 数据结构第三篇【链表的相关知识点一及在线OJ习题】
  • RabbitMQ-发布/订阅模式
  • 客运提质增效新模式!苏州金龙客货邮融合公交闪耀2024道路运输展
  • 【Python实战】使用postman测试flask api接口
  • Docker大学生看了都会系列(二、Mac通过Homebrew安装Docker)
  • 探索 Android Studio 中的 Gemini:加速 Android 开发的新助力
  • linux运维——查看网卡实时流量脚本
  • 【三维重建NeRF(三)】Mip-NeRF论文解读
  • 安卓SystemServer进程详解
  • Android studio 连接 adb传输文件到电脑
  • Web学习篇(二)
  • 在Linux/Ubuntu/Debian系统中使用 `tar` 压缩文件
  • Idea-Linux远程开发部署
  • 智能硬件会是下一个风口行业吗?
  • mysql like 查询优化
  • 3389连接器,3389连接器如何进行安全设置
  • 代码随想录训练营Day56:Leetcode647、516
  • LLM主要类别架构
  • 试比较GD32E230系列与L233/235芯片在IIC上使用温度传感器SHT40的异同
  • 超强算力 Orange Pi Kunpeng Pro 开发板基础测评与体验
  • vs - ms官方查看pdb文件内容的例子工程
  • 【excel】设置二级可变联动菜单
  • 8月1-3日西安国际储能产业博览会