对象和记录是我们在类型级 TypeScript 中可操作的两种最为常见的数据结构。它们将成为我们的类型算法之基石,因此了解它们的工作原理是至关重要的。我相信您已经使用过对象和记录,但是我希望本章能够助您发展对于它们所表示的值的直觉,并向您展示它们藏在冰山一角下的真面貌

type SomeObject = { key1: boolean; key2: number };

type SomeRecord = { [key: string]: number };

在我们开始之前,让我提醒您我们在前一章里讨论过的一些重要概念,因为它们也与本章相关。在深入研究类型与集合论之间的关系之前,我们简要地讨论了类型的五个主要类别ーー原始值、字面量、数据结构、联合和交叉。

我们发现类型表示值的集合。与集合一样,类型也可以包括其他类型,我们称这种关系为子类型化

子类型层次结构

                                                                          子类型层次结构

最后,我们认识了两种特殊的类型:neverーー空集unknownーー包含其它一切的集合。

现在,让我们将目光集中到前一章中所提及的四种数据结构类型中的两种对象记录

type FourKindsOfDataStructures =
  | { key1: boolean; key2: number } // objects
  | { [key: string]: number } // records
  | [boolean, number] // tuples
  | number[]; // arrays

对象类型

对象类型定义 JavaScript 对象集。创建对象类型的语法与我们创建常规对象的方式非常相似:

type User = {
  name: string;
  age: number;
  role: "admin" | "standard";
};

事实上,它们是 JS 对象在类型级别中的等价物。和 JS 对象一样,它们可以包含任意多的属性,并且每个属性都由一个唯一的键进行索引。请注意,每个键可以包含不同的类型name 键包含类型为 string 的值,但是 age 键在这里包含类型为 number 的值。

我们创建的 User 类型是包含一个 name、一个 age 和一个 role 属性的所有对象的集合,这些对象包含正确类型的值:

// ✅ this object is in the `User` set.
const gabriel: User = {
  name: "Gabriel",
  role: "admin",
  age: 28,
};

// ❌
const bob: User = {
  name: "Bob",
  age: 45,
  // <- the `role` key is missing.
};

// ❌
const peter: User = {
  name: "Peter",
  role: "standard",
  age: "45" /* <- the `age` key should be of type `number`,
                   but it's assigned to a `string`. */,
};

但是那些具有额外属性的对象呢? 它们是否也可以分配给我们的 User 类型?

好吧,是的...也不是,听我解释!

对象的可分配性

如果您尝试像我们目前所做的那样,使用显式类型注释将一个对象分配给一个变量,TypeScript 将拒绝额外的属性: