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。 | 优先使用 Array 或 tuple 类型。 | 数字键在 JavaScript 运行时是字符串,数字索引签名是 TypeScript 的“虚构”构造,易造成混淆。 |
在需要处理类数组时使用 T[]。 | 使用 ArrayLike<T> 类型。 | ArrayLike 精确地建模了 length 和数字索引,排除了数组上的其他方法。 |
在 GitHub 上编辑
上次更新于