掌握 TypeScript Record:构建强类型对象 – wiki词典


掌握 TypeScript Record:构建强类型对象

在 TypeScript 的类型系统中,工具类型(Utility Types)扮演着至关重要的角色,它们能够帮助我们以声明式的方式操作类型,从而提升代码的健壮性和可维护性。其中,Record<K, T> 是一个非常强大且常用的工具类型,它允许我们构建一个对象类型,其中属性键的类型为 K,属性值的类型为 T

本文将深入探讨 Record 工具类型,包括其语法、工作原理、常见用例以及与 JavaScript 原生对象和 Map 类型的比较,帮助你更好地掌握如何在项目中构建强类型对象。

1. Record<K, T> 的基本语法与工作原理

Record 的语法非常直观:

typescript
type MyObject = Record<K, T>;

  • K (Keys): 代表对象属性键的类型。K 可以是:
    • stringnumbersymbol 类型。
    • 字符串字面量联合类型(如 'id' | 'name' | 'age')。
    • 枚举类型。
  • T (Values): 代表对象属性值的类型。T 可以是任何 TypeScript 类型,包括基本类型、对象类型、联合类型等。

Record<K, T> 的核心作用是创建一个新的对象类型,该类型强制要求其所有属性的键都必须符合类型 K,并且所有属性的值都必须符合类型 T。这为我们提供了一种在编译时就能确保对象结构一致性的强大机制。

2. 实际应用示例

让我们通过几个例子来深入理解 Record 的用法。

2.1. 键是字符串字面量联合类型

当你知道对象可能包含的特定键集合时,使用字符串字面量联合类型作为 K 是非常合适的。

“`typescript
type UserRole = ‘admin’ | ‘editor’ | ‘viewer’;
type UserPermissions = Record;

const rolePermissions: UserPermissions = {
admin: true,
editor: true,
viewer: false,
};

// ❌ 错误:缺少 ‘viewer’ 属性
// const incompletePermissions: UserPermissions = {
// admin: true,
// editor: true,
// };

// ❌ 错误:’moderator’ 不是 UserRole 中的有效键
// const invalidKeyPermissions: UserPermissions = {
// admin: true,
// editor: true,
// viewer: false,
// moderator: true,
// };

console.log(rolePermissions.admin); // true
“`

这个例子中,UserPermissions 类型确保了任何 UserPermissions 类型的对象都必须包含 admineditorviewer 这三个键,且它们的值必须是 boolean 类型。

2.2. 键是枚举类型

使用枚举类型作为 K 可以增强代码的可读性和维护性,特别是在处理状态或配置时。

“`typescript
enum StatusCode {
Success = 200,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
}

type StatusMessages = Record;

const httpMessages: StatusMessages = {

};

// ❌ 错误:如果缺少任何一个枚举成员,都会报错
// const incompleteMessages: StatusMessages = {
// StatusCode.Success: ‘OK’,
// };

console.log(httpMessagesStatusCode.NotFound); // 资源未找到
“`

这里,StatusMessages 确保了所有 StatusCode 枚举成员都有一个对应的字符串消息。

2.3. 键是 stringnumber

当键的集合不固定,但你希望所有值的类型都保持一致时,可以使用 stringnumber

“`typescript
type UserData = Record;

const userProfile: UserData = {
name: ‘Alice’,
age: 30,
city: ‘New York’,
};

// ✅ 允许:可以添加新的字符串键,只要值类型符合
userProfile.country = ‘USA’;

// ❌ 错误:值类型不符合 (boolean)
// userProfile.isActive = true;

type Scoreboard = Record;

const gameScores: Scoreboard = {
1: { name: ‘Player A’, score: 100 },
2: { name: ‘Player B’, score: 150 },
};
“`

3. Record 的常见用例

Record 工具类型在许多场景下都非常有用:

  • 配置对象 (Configuration Objects): 定义应用的设置,确保每个设置项都具有预期的类型。
  • 查找表/字典 (Lookup Tables/Dictionaries): 创建一个键值对映射,其中键的集合固定,且值的类型一致。
  • API 响应数据结构 (API Response Structuring): 当从 API 获取的数据结构具有已知键和统一值类型时,使用 Record 可以很好地描述。
  • 多语言支持 (Internationalization – i18n): 为不同的语言键提供翻译文本,确保所有语言键都被覆盖。
  • 状态管理 (State Management): 在 Redux 或 Zustand 等状态管理库中定义切片(slice)的状态结构,确保特定部分的状态类型一致。
  • 穷举检查 (Exhaustive Checking): 结合字符串字面量联合类型或枚举,Record 可以帮助你确保处理了所有可能的键,从而实现更严格的类型检查和避免运行时错误。

4. Record 与其他对象表示的比较

理解 Record 的最佳方式之一是将其与其他常见的对象表示方法进行比较。

4.1. Record<K, T> vs. 索引签名 ({ [key: string]: T })

在某些情况下,Record<string, T> 看起来与 { [key: string]: T } 相似,但它们之间存在关键差异。

“`typescript
type IndexSignatureType = { [key: string]: number };
type RecordStringType = Record;

const obj1: IndexSignatureType = { a: 1, b: 2 }; // ✅ 有效
const obj2: RecordStringType = { a: 1, b: 2 }; // ✅ 有效

// 它们的主要区别在于对键的限制:
type SpecificKeysIndex = { [key: ‘foo’ | ‘bar’]: string }; // ❌ 错误:索引签名键必须是 string 或 number
// type SpecificKeysRecord = Record<‘foo’ | ‘bar’, string>; // ✅ 有效,这正是 Record 的强项

const obj3: SpecificKeysRecord = { foo: ‘hello’, bar: ‘world’ };
// const obj4: SpecificKeysRecord = { foo: ‘hello’ }; // ❌ 错误:缺少 ‘bar’
“`

  • 索引签名 ({ [key: string]: T }): 允许对象拥有任意数量的字符串(或数字)键,只要它们的值符合类型 T。它不强制对象包含任何特定的键。
  • Record<K, T>: 当 K 是一个字符串字面量联合类型(如 'a' | 'b')或枚举时,它会强制对象包含所有这些特定的键。如果 Kstringnumber,则行为与索引签名类似,但 Record 提供了更清晰的意图表达。

因此,当你想强制对象包含一组明确定义的键时,Record<K, T> (K 为联合类型或枚举) 是更优的选择,因为它提供了更强的类型安全保证。

4.2. Record vs. Map<K, V>

JavaScript 原生的 Map 对象也用于存储键值对,但

滚动至顶部