如何控制 TypeScript 中索引对象的元素类型

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么这个话题在 Stack Overflow 上如此火爆?

\n\n

如果你曾经用 TypeScript 编写过代码,并试图创建一个所有值都必须是同一类型的对象,那么你很可能遇到过这个问题。问题 "Enforcing the type of the indexed members of a Typescript object" 在 Stack Overflow 上获得了数千次浏览并非偶然——这是从 JavaScript 转来的开发人员常犯的经典错误。

\n\n

想象一下:你有一个缓存字典、一个带有动态键的配置,或者只是一个包含数字的映射。你希望 TypeScript 确保对象中的每个值都是字符串、数字或某种复杂类型。但编译器却莫名其妙地忽略了错误。熟悉吗?

\n\n

在本文中,我们将介绍解决此任务的所有主要方法:从简单的 Index Signatures 到高级的 Mapped Types。你将了解何时使用哪种方法以及会遇到哪些陷阱。

\n\n

问题:具有动态键的对象

\n\n

假设我们正在编写一个用户管理系统。我们有一个对象,其键是用户 ID,值是他们的名字:

\n\n
interface UserCache {\n  [userId: string]: string;\n}\n\nconst cache: UserCache = {\n  'user_1': 'Alice',\n  'user_2': 'Bob',\n  // 'user_3': 123; // 错误!必须是字符串\n};\n\ncache['user_4'] = 'Charlie'; // 正确\ncache['user_5'] = 42; // 错误!类型 'number' 不能赋值给类型 'string'\n
\n\n

看起来一切正常。但有一个问题:Index Signature 允许任何字符串键。如果我们想限制允许的键集呢?

\n\n

方法 1:Index Signature——经典方法

\n\n

Index Signature 是为对象的所有值设置类型的最简单方法。语法:

\n\n
interface StringMap {\n  [key: string]: string;\n}\n\nconst map: StringMap = {\n  hello: 'world',\n  foo: 'bar',\n};\n
\n\n

优点:

\n
    \n
  • 简单易读
  • \n
  • 适用于任意数量的键
  • \n
\n\n

缺点:

\n
    \n
  • 无法限制键集
  • \n
  • 所有值必须是同一类型(或联合类型)
  • \n
  • 不适用于 undefined 值(需要小心)
  • \n
\n\n

新手常犯的错误:尝试向此类对象添加方法:

\n\n
interface BadMap {\n  [key: string]: string;\n  length: number; // 错误!'number' 不能赋值给 'string'\n}\n
\n\n

这行不通,因为 length 违反了通用类型。如果需要混合类型,请使用联合类型或映射类型。

\n\n

方法 2:Record——内置辅助类型

\n\n

TypeScript 提供了内置类型 Record<K, V>,它做同样的事情,但语法更简洁:

\n\n
type StringRecord = Record<string, string>;\n\nconst config: StringRecord = {\n  apiUrl: 'https://api.example.com',\n  timeout: '5000', // 正确,字符串\n  // port: 8080, // 错误!\n};\n\n// 可以限制键:\ntype AllowedKeys = 'name' | 'email' | 'phone';\ntype UserRecord = Record<AllowedKeys, string>;\n\nconst user: UserRecord = {\n  name: 'Alice',\n  email: 'alice@example.com',\n  phone: '+1234567890',\n  // age: '30', // 错误!'age' 不在 AllowedKeys 中\n};\n
\n\n

何时使用:

\n
    \n
  • 需要快速创建类型化字典时
  • \n
  • 键是特定的值集(联合类型)时
  • \n
  • 用于具有已知字段的配置
  • \n
\n\n

缺点: Record 不允许为不同的键设置不同的类型。为此,有映射类型。

\n\n

方法 3:Map——来自 ES6 的替代方案

\n\n

如果你使用 JavaScript 的 Map,TypeScript 可以很好地对其进行类型化

博客

书籍推荐