关于 iterate-objects-safely
这项 TypeScript 技能介绍安全遍历对象的方法,避免不必要地依赖 `any` 或类型断言。它帮助开发者根据数据形状,在 `Object.entries`、已知键列表、断言和 `Map` 之间做选择。
主要功能
- 01通用遍历使用 `Object.entries`
- 02显式列出已知键
- 03仅对封闭对象断言键类型
- 04用 `Map` 进行类型安全遍历
- 05考虑继承属性
适用场景
- 01修复 `for...in` 的索引错误
- 02遍历对象键和值
- 03选择对象或 `Map`
- 04处理含额外属性的对象
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill iterate-objects-safely安装后在 Claude Code 中输入/iterate-objects-safely确认已加载。
详细介绍
了解如何遍历对象
概述
在 TypeScript 中遍历对象出乎意料地棘手。for...in 循环会将键推断为 string,而不是对象的键,从而导致索引错误。这是因为对象可能包含超出其声明类型的额外属性(结构化类型),而且 for...in 会包含继承的属性。
了解安全的遍历模式,有助于你避免使用 any 类型和类型断言,同时正确处理对象遍历。
何时使用此技能
- 遍历对象的键和值
for...in循环产生“Element implicitly has 'any' type”错误Object.entries返回的值类型为any- 需要处理已知和未知的对象属性
- 考虑使用 Map 还是对象存储数据
铁律
使用 Object.entries 安全地遍历任何对象。只有在确切了解对象结构时,才使用带 keyof 断言的 for...in。若要确保类型安全,可以考虑使用 Map。
检测
留意以下错误:
// ERROR: Element implicitly has 'any' type
for (const k in obj) {
const v = obj[k]; // Error!
}
// ERROR: Type 'string' cannot be used to index type 'ABC'
function foo(abc: ABC) {
for (const k in abc) {
const v = abc[k]; // Error!
}
}
问题所在
interface ABC {
a: string;
b: string;
c: number;
}
function foo(abc: ABC) {
for (const k in abc) {
// k is string, not 'a' | 'b' | 'c'
const v = abc[k];
// ^? any - TypeScript gives up
}
}
// Why? Structural typing allows extra properties:
const x = { a: 'a', b: 'b', c: 2, d: new Date() };
foo(x); // Valid! x has at least ABC's properties
// k could be 'd', which isn't in ABC
使用 Object.entries 安全遍历
function foo(abc: ABC) {
for (const [k, v] of Object.entries(abc)) {
// k: string (honest about what it is)
// v: any (honest about unknown values)
console.log(k, v);
}
}
优点:始终安全,无需类型断言
缺点:值的类型为 any,键的类型为 string
遍历已知键
当你确切知道有哪些键时:
const obj = { one: 'uno', two: 'dos', three: 'tres' };
// Explicitly list keys
const keys = ['one', 'two', 'three'] as const;
for (const k of keys) {
// k: 'one' | 'two' | 'three'
const v = obj[k]; // string - precise!
}
对封闭对象使用类型断言
当你确信对象的结构时:
const obj = { one: 'uno', two: 'dos', three: 'tres' };
for (const kStr in obj) {
const k = kStr as keyof typeof obj;
// k: 'one' | 'two' | 'three'
const v = obj[k]; // OK
}
警告:只有在你控制对象的创建,并且确定它没有额外属性时,这种做法才安全。
Map:类型安全的替代方案
const m = new Map([
['one', 'uno'],
['two', 'dos'],
['three', 'tres'],
]);
for (const [k, v] of m) {
// k: string (known type)
// v: string (known type)
console.log(k, v);
}
优点:类型有保证,不会发生原型污染 缺点:处理 JSON 数据不够方便,API 也不同
实际示例
interface Config {
host: string;
port: number;
ssl: boolean;
}
// Safe iteration with Object.entries
function printConfig(config: Config) {
for (const [key, value] of Object.entries(config)) {
console.log(`${key}: ${value}`);
}
}
// Type-safe with known keys
function validateConfig(config: Config): string[] {
const errors: string[] = [];
const requiredKeys = ['host', 'port', 'ssl'] as const;
for (const key of requiredKeys) {
if (!(key in config)) {
errors.push(`Missing: ${key}`);
}
}
return errors;
}
压力应对规范
当对象遍历导致类型问题时:
- 尝试使用 Object.entries:最安全的选项,适用于任何对象
- 考虑使用 Map:如果数据结构由你控制
- 显式列出键:当键在编译时已知
- 谨慎使用类型断言:只有在确定对象结构时才使用
警示信号
| 反模式 | 问题 | 解决方案 |
|---|---|---|
在 for...in 中使用 obj[k] | k 是 string,索引不安全 | 使用 Object.entries |
对参数使用 as keyof T | 对象可能包含额外键 | 使用 Object.entries 或 Map |
| 忽略继承的属性 | 存在原型污染风险 | Object.entries 不会包含这些属性 |
常见借口
“我直接用 as any 就行”
事实:Object.entries 会以更诚实的方式提供相同的 any。无需绕过类型安全。
“这个对象永远不会有额外属性”
事实:由于 TypeScript 使用结构化类型,它可能会有额外属性。请明确说明你的假设。
“Map 太冗长了”
事实:它确实更冗长,但类型安全性也更高。对于关键代码来说值得使用。
快速参考
| 方式 | 键类型 | 值类型 | 安全性 |
|---|---|---|---|
for...in | string | any(错误) | 不安全 |
Object.entries | string | any | 安全 |
| 已知键数组 | 特定类型 | 特定类型 | 安全 |
keyof 断言 | 特定类型 | 特定类型 | 有风险 |
| Map | 特定类型 | 特定类型 | 安全 |
总结
由于结构化类型和原型污染,遍历对象比较棘手。使用 Object.entries 确保安全,使用显式键数组确保精确,或使用 Map 确保类型安全。
参考资料
- Dan Vanderkam 著《Effective TypeScript》第 2 版
- 条目 60:了解如何遍历对象