TypeScript Record 类型完全指南
TypeScript 的 Record 类型简化了具有一致值类型的对象结构管理。本指南涵盖了 Record 的基本知识,包括其定义、语法以及它与元组等其他类型的区别。我们将学习如何在实际场景中定义和使用 Record,例如强制穷尽性案例处理和枚举映射。此外,我们还将探索通过将 Record 与 Partial、Pick 和 Readonly 等实用类型结合使用的高级用法。
引言
Record 类型是一种实用类型,允许我们创建具有指定键和统一值类型的对象类型。这种类型特别适用于定义映射并确保对象中的所有值都符合单一类型。
Record 类型的定义
TypeScript 文档中的官方定义是:
其中:
例如,Record<string, number>
定义了一个对象,其中每个键都是字符串,每个值都是数字。这种类型确保对象的所有属性都具有相同的值类型,但键可以变化。
Record 与元组的比较
Record 和元组都用于处理数据集合,但它们有不同的用途。即使它们存储多个值,它们在结构和用法上也有所不同。Record 具有具有固定类型的命名属性,而元组是由其位置标识的元素的有序列表。以下是一个简单的比较:
例如,考虑以下内容。
这是一个 Record 类型,它将字符串键映射到数字值:
type AgeMap = Record<string, number>;
复制代码
一个元组类型表示一个具有字符串(名称)和数字(年龄)的固定位置的数组:
type Person = [string, number];
复制代码
Record 类型的基本用法
Record 类型提供了一种简单有效的方法来将键映射到值。当我们需要定义具有特定键值对的对象时,它特别有用,其中键是特定类型,值是另一种类型。
以下是一些使用 Record 类型定义和创建结构化数据的基本方法。
定义 Record
要定义 Record,我们指定键和值的类型。下面的示例定义了一个对象,其中每个键都是字符串,每个值也是字符串。这可以用于用户数据的通用映射:
type User = Record<string, string>;
复制代码
创建 Record 类型示例
一些网站有各种子域。假设每个子域都需要某种级别的管理员访问权限,并创建一个 Record 类型来存储不同的管理员角色及其相应的访问级别。这里,UserRoles 和 UserStatus 是 Record 类型,其中键是特定的字符串字面量(admin、blogAdmin、docsAdmin、active、inactive、suspended),值是描述每个角色和状态的字符串。
首先,我们定义一个 Record 类型 UserRoles,其中特定的管理员角色作为键,它们的描述作为值。UserRoles 类型确保任何此类型的对象都将具有键 admin、blogAdmin 和 docsAdmin,以及描述每个角色的字符串值。roles 对象通过为每个管理员角色提供描述来遵循此类型:
type UserRoles = Record<'admin' | 'blogAdmin' | 'docsAdmin', string>;
const roles: UserRoles = {
admin: 'General Administrator with access to all areas.',
blogAdmin: 'Administrator with access to blog content.',
docsAdmin: 'Administrator with access to documentation.'
};
复制代码
接下来,我们定义一个 Record 类型 UserStatus,其中特定的状态作为键,它们的描述作为值。UserStatus 类型确保任何此类型的对象都将具有键 active、inactive 和 suspended,以及描述每个状态的字符串值。userStatus 对象通过为每个状态提供描述来遵循此类型:
type UserStatus = Record<'active' | 'inactive' | 'suspended', string>;
const userStatus: UserStatus = {
active: 'User is currently active and can use all features.',
inactive: 'User is currently inactive and cannot access their account.',
suspended: 'User account is suspended due to policy violations.'
};
复制代码
通过以这种方式创建 Record 类型,我们确保管理员角色和用户状态在整个应用程序中得到良好定义和一致。
Record 类型的实际用例
在本节中,我们将回顾 Record 类型的几个实际用例,以展示其在不同场景中的多功能性和有效性。
用例 1:强制穷尽性案例处理
使用 Record 定义案例值和消息之间的映射允许我们显式处理每个可能的案例。这确保所有案例都被覆盖,并且任何缺失的案例都会导致编译时错误。
在下面的示例中,statusMessages 是一个 Record,其中键是特定的 Status 值('pending'、'completed'、'failed'),每个键映射到相应的消息。getStatusMessage 函数使用此记录根据状态参数返回适当的消息。这种方法保证所有状态都得到正确和一致的处理。
示例:
type Status = 'pending' | 'completed' | 'failed';
interface StatusInfo {
message: string;
severity: 'low' | 'medium' | 'high';
retryable: boolean;
}
const statusMessages: Record<Status, StatusInfo> = {
pending: {
message: 'Your request is pending.',
severity: 'medium',
retryable: true,
},
completed: {
message: 'Your request has been completed.',
severity: 'low',
retryable: false,
},
failed: {
message: 'Your request has failed.',
severity: 'high',
retryable: true,
},
};
function getStatusMessage(status: Status): string {
const info = statusMessages[status];
return `${info.message} Severity: ${info.severity}, Retryable: ${info.retryable}`;
}
// 请求成功的情况。
console.log(getStatusMessage('completed')); // Your request has been completed. Severity: low, Retryable: false
复制代码
用例 2:使用泛型强制应用程序中的类型检查
TypeScript 中的泛型允许灵活和可重用的代码。当与 Record 结合时,泛型可以帮助强制类型检查并确保对象符合特定结构。
通过将泛型与 Record 一起使用,我们可以创建具有特定键集和一致值类型的函数或实用程序来生成对象。这种方法增强了我们代码库中的类型安全性和可重用性。
在下面的示例中,createRecord 函数接受一个键数组和一个值,并返回一个 Record,其中每个键映射到提供的值。此函数使用泛型(K 表示键,T 表示值类型)来确保生成的 Record 具有正确的结构。
示例:
function createRecord<K extends string, T>(keys: K[], value: T): Record<K, T> {
const record: Partial<Record<K, T>> = {};
keys.forEach(key => record[key] = value);
return record as Record<K, T>;
}
interface RoleInfo {
description: string;
permissions: string[];
}
const userRoles = createRecord(['admin', 'editor', 'viewer'], {
description: 'Default role',
permissions: ['read'],
});
console.log(userRoles);
/*
//输出:
{
admin: { description: 'Default role', permissions: ['read'] },
editor: { description: 'Default role', permissions: ['read'] },
viewer: { description: 'Default role', permissions: ['read'] }
}
*/
复制代码
用例 3:将枚举映射到数据
使用 Record 将枚举映射到数据允许我们创建一个查找表,其中每个枚举值都与特定信息相关联。这对于基于枚举值配置设置等场景特别有用。
在此示例中,colorHex 是一个 Record,它将每个 Color 枚举值映射到其相应的十六进制颜色代码。这种方法提供了一种清晰且类型安全的方式来处理基于枚举值的颜色相关数据。
示例:
enum Color {
Red = 'RED',
Green = 'GREEN',
Blue = 'BLUE',
Yellow = 'YELLOW'
}
interface ColorInfo {
hex: string;
rgb: string;
complementary: string;
}
const colorHex: Record<Color, ColorInfo> = {
[Color.Red]: {
hex: '#FF0000',
rgb: 'rgb(255, 0, 0)',
complementary: '#00FFFF',
},
[Color.Green]: {
hex: '#00FF00',
rgb: 'rgb(0, 255, 0)',
complementary: '#FF00FF',
},
[Color.Blue]: {
hex: '#0000FF',
rgb: 'rgb(0, 0, 255)',
complementary: '#FFFF00',
},
[Color.Yellow]: {
hex: '#FFFF00',
rgb: 'rgb(255, 255, 0)',
complementary: '#0000FF',
},
};
console.log(colorHex[Color.Green]); //输出: { hex: '#00FF00', rgb: 'rgb(0, 255, 0)', complementary: '#FF00FF' }
复制代码
用例 4:创建查找表
使用 Record 的查找表有助于将键(如标识符、名称)映射到特定值(如描述、代码)。这对于各种应用程序非常有用,包括配置、翻译和许多其他事情。
这里,countryCode 是一个 Record,它将国家代码映射到各自的国家名称、人口、首都和大陆。此查找表允许基于国家代码快速且类型安全地检索国家名称和人口。
示例:
type CountryCode = "US" | "CA" | "MX" | "JP";
interface CountryInfo {
name: string;
population: number;
capital: string;
continent: string;
}
const countryLookup: Record<CountryCode, CountryInfo> = {
US: {
name: "United States",
population: 331000000,
capital: "Washington D.C.",
continent: "North America",
},
CA: {
name: "Canada",
population: 37700000,
capital: "Ottawa",
continent: "North America",
},
MX: {
name: "Mexico",
population: 128000000,
capital: "Mexico City",
continent: "North America",
},
JP: {
name: "Japan",
population: 126300000,
capital: "Tokyo",
continent: "Asia",
},
};
console.log(countryLookup.US);
/*
//输出:
{
name: "United States",
population: 331000000,
capital: "Washington D.C.",
continent: "North America"
}
*/
console.log(countryLookup.US.population);//输出: 331000000,
复制代码
迭代 Record 类型
迭代 Record 类型对于访问和操作数据结构中的数据非常重要。让我们创建一些示例数据,并展示如何迭代 TypeScript Record 类型的各种方法。
示例数据:
interface Course {
professor: string;
credits: number;
students: string[];
}
interface Courses {
[key: string]: Course;
}
const courses: Courses = {
Math101: {
professor: "Dr. Eze",
credits: 3,
students: ["Emmanuel", "Bob", "Charlie"],
},
History201: {
professor: "Dr. Jones",
credits: 4,
students: ["Dave", "Eve"],
},
};
复制代码
使用 forEach
要使用 forEach 与 Record,将其转换为键值对数组:
Object.entries(courses).forEach(([key, value]) => {
console.log(`${key}: ${value.professor}, ${value.credits}`);
value.students.forEach(student => {
console.log(`Student: ${student}`);
});
});
/*
//输出:
Math101: Dr. Eze, 3
Student: Emmanuel
Student: Bob
Student: Charlie
History201: Dr. Jones, 4
Student: Dave
Student: Eve
*/
复制代码
使用 for...in
for...in 循环迭代 Record 的键:
for (const key in courses) {
if (courses.hasOwnProperty(key)) {
const course = courses[key];
console.log(`${key}: ${course.professor}, ${course.credits}`);
course.students.forEach(student => {
console.log(`Student: ${student}`);
});
}
}
/*
//输出:
Math101: Dr. Eze, 3
Student: Emmanuel
Student: Bob
Student: Charlie
History201: Dr. Jones, 4
Student: Dave
Student: Eve
*/
复制代码
使用 Object.keys()
Object.keys()返回 Record 键的数组:
Object.keys(courses).forEach((key) => {
const course = courses[key];
console.log(`${key}: ${course.professor}, ${course.credits}`);
course.students.forEach(student => {
console.log(`Student: ${student}`);
});
});
/*
//输出:
Math101: Dr. Eze, 3
Student: Emmanuel
Student: Bob
Student: Charlie
History201: Dr. Jones, 4
Student: Dave
Student: Eve
*/
复制代码
使用 Object.values()
Object.values()返回 Record 值的数组:
Object.values(courses).forEach((course) => {
console.log(`${course.professor}, ${course.credits}`);
course.students.forEach(student => {
console.log(`Student: ${student}`);
});
});
/*
//输出:
Dr. Eze, 3
Student: Emmanuel
Student: Bob
Student: Charlie
Dr. Jones, 4
Student: Dave
Student: Eve
*/
复制代码
Record 的高级用法和实用类型
Record 类型可以与其他实用类型结合使用,以实现更大的灵活性和类型安全性。本节揭示了高级使用模式,演示了 Record 如何与 Pick、Readonly 和 Partial 等实用类型一起工作。
将 Record 与 Pick 结合用于选择性类型映射
Pick 实用类型允许我们通过从现有类型中选择特定属性来创建新类型。当我们只想使用较大类型中的属性子集时,这非常有用。
在这里,我们通过从 ProductInfo 接口中仅选择 name 和 price 属性创建了一个新类型 SelectedProductInfo,然后使用 Record 将不同的产品映射到此新类型:
interface ProductInfo {
name: string;
price: number;
category: string;
}
type SelectedProductInfo = Pick<ProductInfo, "name" | "price">;
type Product = 'Laptop' | 'Smartphone' | 'Tablet';
const products: Record<Product, SelectedProductInfo> = {
"Laptop": { name: "Dell XPS 15", price: 1500 },
"Smartphone": { name: "iPhone 12", price: 999 },
"Tablet": { name: "iPad Pro", price: 799 }
};
复制代码
将 Record 与 Readonly 结合用于不可变属性
Readonly 实用类型确保属性在设置后不能被修改。这对于创建不可变数据结构非常有用。
下面示例中的 ReadonlyProductInfo 类型使 ProductInfo 的所有属性不可变,确保每个产品的详细信息在定义后不能被更改:
type ReadonlyProductInfo = Readonly<ProductInfo>;
const readonlyProducts: Record<Product, ReadonlyProductInfo> = {
"Laptop": { name: "Dell XPS 15", price: 1500, category: "Electronics" },
"Smartphone": { name: "iPhone 12", price: 999, category: "Electronics" },
"Tablet": { name: "iPad Pro", price: 799, category: "Electronics" }
};
复制代码
将 Record 与 Partial 结合用于可选属性
Partial 实用类型使类型的所有属性变为可选。这对于可能不知道或不需要所有属性的场景非常有用。
这里,PartialProductInfo 类型允许我们创建具有 ProductInfo 中定义的一些或没有属性的产品,提供了在指定产品信息时的灵活性:
type PartialProductInfo = Partial<ProductInfo>;
const partialProducts: Record<Product, PartialProductInfo> = {
"Laptop": { name: "Dell XPS 15" },
"Smartphone": { price: 999 },
"Tablet": {}
};
复制代码
将 Record 与 Record 结合用于嵌套映射
另一个高级用法涉及组合 Record 类型以创建嵌套映射,这对于管理复杂数据结构特别有用。
在此示例中,storeInventory 使用嵌套 Record 类型将部门映射到其各自的产品和详细信息,演示了如何组合 Record 以进行更复杂的数据管理:
type Department = 'Electronics' | 'Furniture';
type ProductDetails = Record<Product, ProductInfo>;
const storeInventory: Record<Department, ProductDetails> = {
"Electronics": {
"Laptop": { name: "Dell XPS 15", price: 1500, category: "Electronics" },
"Smartphone": { name: "iPhone 12", price: 999, category: "Electronics" },
"Tablet": { name: "iPad Pro", price: 799, category: "Electronics" }
},
"Furniture": {
"Chair": { name: "Office Chair", price: 200, category: "Furniture" },
"Table": { name: "Dining Table", price: 500, category: "Furniture" },
"Sofa": { name: "Living Room Sofa", price: 800, category: "Furniture" }
}
};
复制代码
结论
Record 类型是管理和结构化对象类型的多功能工具,因为它允许我们定义键和值之间的清晰映射,确保代码中的类型安全性和一致性。
有关更详细的信息,请参阅 TypeScript 文档,并查看其他额外资源,如 Total TypeScript 和 TypeScript Tutorial,以加深对 TypeScript Record 类型系统的理解。更多精彩内容 请关注我的个人公众号 公众号(办公 AI 智能小助手)公众号二维码
办公AI智能小助手
评论