mcpField
I
iterate-objects-safely

iterate-objects-safely

github.com/marius-townhouse开发645 次安装38 周前更新
代码仓库

关于 iterate-objects-safely

这项 TypeScript 技能介绍安全遍历对象的方法,避免不必要地依赖 `any` 或类型断言。它帮助开发者根据数据形状,在 `Object.entries`、已知键列表、断言和 `Map` 之间做选择。

主要功能

  1. 01通用遍历使用 `Object.entries`
  2. 02显式列出已知键
  3. 03仅对封闭对象断言键类型
  4. 04用 `Map` 进行类型安全遍历
  5. 05考虑继承属性

适用场景

  1. 01修复 `for...in` 的索引错误
  2. 02遍历对象键和值
  3. 03选择对象或 `Map`
  4. 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;
}

压力应对规范

当对象遍历导致类型问题时:

  1. 尝试使用 Object.entries:最安全的选项,适用于任何对象
  2. 考虑使用 Map:如果数据结构由你控制
  3. 显式列出键:当键在编译时已知
  4. 谨慎使用类型断言:只有在确定对象结构时才使用

警示信号

反模式问题解决方案
在 for...in 中使用 obj[k]k 是 string,索引不安全使用 Object.entries
对参数使用 as keyof T对象可能包含额外键使用 Object.entries 或 Map
忽略继承的属性存在原型污染风险Object.entries 不会包含这些属性

常见借口

“我直接用 as any 就行”

事实:Object.entries 会以更诚实的方式提供相同的 any。无需绕过类型安全。

“这个对象永远不会有额外属性”

事实:由于 TypeScript 使用结构化类型,它可能会有额外属性。请明确说明你的假设。

“Map 太冗长了”

事实:它确实更冗长,但类型安全性也更高。对于关键代码来说值得使用。

快速参考

方式键类型值类型安全性
for...instringany(错误)不安全
Object.entriesstringany安全
已知键数组特定类型特定类型安全
keyof 断言特定类型特定类型有风险
Map特定类型特定类型安全

总结

由于结构化类型和原型污染,遍历对象比较棘手。使用 Object.entries 确保安全,使用显式键数组确保精确,或使用 Map 确保类型安全。

参考资料

  • Dan Vanderkam 著《Effective TypeScript》第 2 版
  • 条目 60:了解如何遍历对象

评论

支持 Markdown