Effective TypeScript

避免数字索引签名

核心原则: 避免在自定义类型中使用 [n: number]: T 形式的数字索引签名。在 TypeScript 中,数字索引签名是对 JavaScript 数组模型的类型虚构(fiction),而非实际运行时特性。

1. JavaScript 对象模型的特性

在 JavaScript 运行时环境中,对象键通常是字符串或 Symbol。

数字键在运行时会被转换为字符串:

如果试图使用数字作为对象属性名,JavaScript 运行时会自动将其转换为字符串。

const obj = { 1: 2, 3: 4 };
// 运行时 obj 实际上是:
// { '1': 2, '3': 4 }

数组(Array)的本质:

JavaScript 中的数组也是对象 (typeof [] 为 'object')。数组的数字索引在底层也是字符串键。

您可以使用字符串键访问数组元素,这在 JavaScript 中是有效的:

const x =;
console.log(x['1']); // 2
console.log(Object.keys(x)); // [ '0', '1', '2' ] (返回字符串键)

2. TypeScript 对数组的类型虚构

TypeScript 为了提供便利并帮助捕获错误,在类型系统中允许数字键并将其与字符串键区分开来。

Array<T> 的类型声明中,就存在数字索引签名:

// 摘自 lib.es5.d.ts
interface Array<T> {
  // ... 其他属性和方法
  [n: number]: T; 
  // ...
}

这种数字索引签名是一种纯粹的 TypeScript 构造,其目的是帮助捕获错误。然而,这创建了一个令人困惑的模式:当你使用数字索引签名时,TypeScript 期望索引是 number 类型,但您通过 Object.keys 取出的键仍然是 string

类型检查中的应用:

数字索引签名有助于避免使用非数字索引访问数组:

const xs =;

// OK: 字面量数字或字符串化的数字常量被允许
const x0 = xs;
const x1 = xs['1'];

// 错误:输入值类型不是数字
const inputEl = { value: "not-a-number" };
// @ts-expect-error
const xN = xs[inputEl.value]; 
// ~~~~~ Index expression is not of type 'number'.

3. 推荐替代方案

鉴于数字索引签名只是一种“虚构”,并且容易导致误解,通常没有理由在自定义类型中将其用于索引

优先使用 Array 或 Tuple

如果需要指定可以通过数字索引访问的内容,最合适的做法是使用内置的 Array 类型或 tuple(元组)类型。

// 推荐使用数组或元组类型,而非自定义数字索引签名
type TArray = string[];
type TTuple = [number, string, boolean];

const arr: TArray = ['a', 'b'];
const tuple: TTuple = [10, 'hello', true];

使用 ArrayLike 应对类数组结构

如果您需要接受任何具有 length 属性和数字索引访问能力的类数组结构(例如 NodeList),应使用 ArrayLike<T> 类型。

// ArrayLike<T> 包含 length 属性和数字索引签名
function processArrayLike<T>(xs: ArrayLike<T>): T {
  // 可以安全地访问 length 和数字索引
  return xs[xs.length - 1]; 
}

// 示例:可以接受具有正确结构的自定义对象
const customList: ArrayLike<string> = {
  '0': 'First',
  '1': 'Second',
  length: 2,
}; // OK

processArrayLike(customList); // 'Second'

总结要点

避免的做法推荐的做法理由
在自定义类型中使用 [n: number]: T优先使用 Arraytuple 类型。数字键在 JavaScript 运行时是字符串,数字索引签名是 TypeScript 的“虚构”构造,易造成混淆。
在需要处理类数组时使用 T[]使用 ArrayLike<T> 类型。ArrayLike 精确地建模了 length 和数字索引,排除了数组上的其他方法。
在 GitHub 上编辑

上次更新于