在没有非常了解 Record 之前,定义对象的类型,一般使用 interface。它是 TS 中定义数据结构的一种方式,用来描述对象的形状、函数类型、类的结构等。
// 基本用法
interface User {
name: string;
age: number;
isAdmin: boolean;
}
const user: User = {
name: "Alice",
age: 30,
isAdmin: true,
};
而在 TypeScript 中,提供了一个实用工具类型 --- Record,用于构建对象类型的映射。它允许我们创建一个对象类型,其中所有键的类型和值的类型都支持自定义。
1. 基本用法
Record<K, T> 类型有两个参数:
- K:键的类型(通常是 string、number 或 symbol,或者它们的联合类型)
- V:值的类型
举个 🌰
type UserRoles = 'admin' | 'user' | 'guest';
type RolePermissions = Record<UserRoles, string[]>;
const permissions: RolePermissions = {
admin: ['read', 'write', 'delete'],
user: ['read', 'write'],
guest: ['read']
};
Record<UserRoles, string[]> 定义一个对象类型,其中键必须是 'admin'、'user' 或者 guest',而值是字符串数组。
2. 注意事项
1、键的类型
Record<K, T> 要求键的类型必须是 string、number 或 symbol 类型的子类型。因此不能使用 boolean 或 自定义对象作为键类型。
错误 🌰
type InvalidRecord = Record<boolean, string>;
2、覆盖问题
如果 K 类型是联合类型,Record 会要求每个键都必须出现在对象中。这意味着即使我们只需要部分键,但是必须声明所有键。
举个 🌰
type Status = 'success' | 'error' | 'pending';
const statusMessages: Record<Status, string> = {
success: 'Operation was successful',
error: 'There was an error',
pending: 'Operation is pending',
};
若是某一项不写,vscode 会给与提示信息。
3. 与 interface 对比
1、联系
二者都可用于描述对象结构,确保对象属性符合指定类型。
2、区别
1)用途与场景
如果需要定义一个具有复杂结构、方法或需要被多个类实现的类型,选择 interface。
如果是用来定义一组键值对,尤其是当键的类型可以枚举或确定是,选择 Record 更加简洁。
2)扩展性
interface 可扩展,支持继承,通过 extends 关键字扩展已有接口,从而增加或修改属性。
Record 不可扩展,只是一个工具类型。
举个 🌰
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
const myDog: Dog = {
name: "Buddy",
breed: "Golden Retriever"
};
Record 无法直接扩展,但可以使用交叉类型(&)将 Record 和另一个类型结合。
type Animal = {
name: string;
};
type Dog = Record<"breed", string> & Animal;
const myDog: Dog = {
name: "Buddy",
breed: "Golden Retriever"
};
3)描述方法
Record 不能描述对象的方法,仅用于定义键值对类型,因此定义方法需要使用 interface。
interface Person {
name: string;
getName(): string;
setName(value: string): void;
}
let person: Person = {
name: "Alice",
getName: function () {
return this.name;
},
setName: function (value: string) {
this.name = value;
}
};
4)声明合并
TypeScript 支持接口的声明合并,这意味同名接口会自动合并属性,而 Record 做不到。
举个 🌰
// 假设我们有一个接口
interface LibraryBook {
title: string;
author: string;
}
// 继续扩展这个接口
interface LibraryBook {
publishedYear: number;
}
// 现在 LibraryBook 接口同时拥有了 title, author 和 publishedYear 属性
// Record 不支持声明合并
type BookRecord = Record<string, any>;
// 下面的声明不会合并到 BookRecord,而是创建了一个新的类型别名
type BookRecord = {
publishedYear: number;
};