灵活性:Record 类型的字段顺序无关紧要,可以通过名称访问任意字段;Tuple 类型的元素顺序固定,必须通过位置访问。
type Course = "Computer Science" | "Mathematics" | "Literature" interface CourseInfo { professor: string cfu: number } const courses: Record<Course, CourseInfo> = { "Computer Science": { professor: "Mary Jane", cfu: 12 }, "Mathematics": { professor: "John Doe", cfu: 12 }, "Literature": { professor: "Frank Purple", cfu: 12 } }在这个例子中,我们定义了一个名为 Course 的类型,用于列出课程名称,以及一个名为 CourseInfo 的类型,用于保存课程的一些基本信息。然后,我们使用 Record 类型将每个 Course 与其 CourseInfo 进行匹配。
console.log(courses["Literature"])上面的代码将输出以下内容:
{ "professor": "Frank Purple", "cfu": 12 }用例 1:强制穷尽情况处理
type Discriminator = 1 | 2 | 3; // 堆代码 duidaima.com function factory(d: Discriminator): string { switch(d) { case 1: return "1"; case 2: return "2"; case 3: return "3"; default: return "0"; } }然而,如果我们向 Discriminator 添加一个新值,由于有默认分支,TypeScript 不会告诉我们工厂函数中未处理新的情况。如果没有默认分支,TypeScript 将检测到 Discriminator 中添加了新值。
type Discriminator = 1 | 2 | 3; function factory(d: Discriminator): string { const factories: Record<Discriminator, () => string> = { 1: () => "1", 2: () => "2", 3: () => "3" }; return factories[d](); } console.log(factory(1));新的工厂函数定义了一个 Record,将 Discriminator 与特定的初始化函数匹配,这些函数不需要输入参数并返回一个字符串。然后,工厂函数根据 d: Discriminator 获取正确的函数,并通过调用结果函数返回一个字符串。如果现在向 Discriminator 添加更多元素,Record 类型将确保 TypeScript 检测到 factories 中缺少的情况。
class Result<Properties = Record<string, any>> { constructor( public readonly properties: Record< keyof Properties, Properties[keyof Properties] > ) {} }Result 类看起来有点复杂。在这个例子中,我们将其声明为一个泛型类型,其中类型参数 Properties 默认为 Record<string, any>。使用 any 可能看起来不太理想,但这是有道理的。正如我们稍后将看到的,Record 将属性名称映射到属性值,因此我们无法提前知道属性的类型。此外,为了使其尽可能可重用,我们必须使用 TypeScript 中最抽象的类型——确实是 any!
每个键的值将是 Properties 记录中相应属性的值。
interface CourseInfo { title: string; professor: string; cfu: number; } const course = new Result<CourseInfo>({ title: "文学", professor: "Mary Jane", cfu: 12 }); console.log(course.properties.title); //console.log(course.properties.students); // 这一行将无法编译!在上面的代码中,我们定义了一个 CourseInfo 接口,模型化我们想要存储和查询的基本信息:课程名称、教授的姓名和学分数。接下来,我们模拟创建一个课程。这只是一个字面值,但你可以想象它是数据库查询或 HTTP 调用的结果。
// `CourseInfo` 和 `Result` 如上所述 const course = new Result<CourseInfo & Record<string, any>>({ title: "文学", professor: "Mary Jane", cfu: 12, webpage: "https://..." }); console.log(course.properties.webpage); // 输出: "https://..."Record 类型 vs. Map 类型
Map 类型:允许任意类型的键,灵活性更高,可以使用字符串、数字、对象、函数等作为键。这使得 Map 类型在处理动态生成键或非字符串类型的键时更加适用。
Map 类型:适用于需要灵活键类型和保持插入顺序的场景。它允许任意类型的键,适用于更动态和复杂的数据结构。
type Course = "Computer Science" | "Mathematics" | "Literature"; interface CourseInfo { professor: string; cfu: number; } const courses: Record<Course, CourseInfo> = { "Computer Science": { professor: "Mary Jane", cfu: 12 }, "Mathematics": { professor: "John Doe", cfu: 12 }, "Literature": { professor: "Frank Purple", cfu: 12 }, }; Object.entries(courses).forEach(([key, value]) => { console.log(`${key}: ${value.professor}, ${value.cfu}`); });使用 for...in
for (const key in courses) { if (courses.hasOwnProperty(key)) { const course = courses[key as Course]; console.log(`${key}: ${course.professor}, ${course.cfu}`); } }使用 Object.keys()
Object.keys(courses).forEach((key) => { const course = courses[key as Course]; console.log(`${key}: ${course.professor}, ${course.cfu}`); });
Object.values(courses).forEach((course) => { console.log(`${course.professor}, ${course.cfu}`); });使用 Object.entries()
Object.entries(courses).forEach(([key, value]) => { console.log(`${key}: ${value.professor}, ${value.cfu}`); });TypeScript Record 类型的高级用例
interface CourseInfo { professor: string; cfu: number; semester: string; students: number; } type SelectedCourseInfo = Pick<CourseInfo, "professor" | "cfu">; type Course = "Computer Science" | "Mathematics" | "Literature"; const courses: Record<Course, SelectedCourseInfo> = { "Computer Science": { professor: "Mary Jane", cfu: 12 }, "Mathematics": { professor: "John Doe", cfu: 12 }, "Literature": { professor: "Frank Purple", cfu: 12 }, };在上面的例子中,我们使用 Pick<CourseInfo, "professor" | "cfu"> 创建了一个新类型 SelectedCourseInfo,该类型仅包括 CourseInfo 中的 professor 和 cfu 属性。然后,我们定义了一个 Record 类型,将每个 Course 映射到 SelectedCourseInfo。
type PreferenceKey = string; // 动态键 type PreferenceValue = string | boolean | number; // 值 interface UserPreferences { [key: string]: PreferenceValue; } const userPreferences: Record<PreferenceKey, PreferenceValue> = { theme: "dark", notifications: true, fontSize: 14, }; // 更新偏好设置 userPreferences.language = "English"; // 遍历偏好设置 Object.entries(userPreferences).forEach(([key, value]) => { console.log(`${key}: ${value}`); });在上述代码中,我们将 PreferenceKey 定义为字符串,并将 PreferenceValue 定义为可以是字符串、布尔值或数字的联合类型。UserPreferences 接口使用索引签名来表示具有 PreferenceValue 类型的动态键。然后我们使用 Record<PreferenceKey, PreferenceValue> 创建一个 userPreferences 字典,用于存储和遍历动态用户偏好设置。
type ReadonlyCourseInfo = Readonly<CourseInfo>; const readonlyCourses: Record<Course, ReadonlyCourseInfo> = { "Computer Science": { professor: "Mary Jane", cfu: 12, semester: "Fall", students: 100 }, "Mathematics": { professor: "John Doe", cfu: 12, semester: "Spring", students: 80 }, "Literature": { professor: "Frank Purple", cfu: 12, semester: "Fall", students: 60 }, }; // Trying to modify a readonly property will result in a compile-time error // readonlyCourses["Computer Science"].cfu = 14; // Error: Cannot assign to 'cfu' because it is a read-only property.在上面的代码中,Readonly<CourseInfo> 确保了 CourseInfo 的所有属性都是只读的,防止任何修改。尝试修改只读属性将导致编译时错误:
readonlyCourses["Computer Science"].cfu = 14; // 错误:不能给 'cfu' 赋值,因为它是只读属性。使用 Partial 与 Record
type PartialCourseInfo = Partial<CourseInfo>; const partialCourses: Record<Course, PartialCourseInfo> = { "Computer Science": { professor: "Mary Jane" }, "Mathematics": { cfu: 12 }, "Literature": {}, };总结