如何在TypeScript中迭代对象键 | Object.keys 为何不起作用?

原文:How to Iterate Over Object Keys in TypeScript

链接:www.totaltypescript.com/iterate-ove...

作者:Matt Pocock

在TypeScript中迭代对象键object keys可能是一场噩梦。以下是我所知道的所有解决方案。

快速解释 - 省流

使用 Object.keys 迭代不起作用,是因为 Object.keys 返回一个字符串数组,而不是所有键的并集。这是设计出来的,不会改变。

ts 复制代码
function printUser(user: User) {
  Object.keys(user).forEach((key) => {
    // Doesn't work!
    console.log(user[key]);
    // Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'User'.
    // No index signature with a parameter of type 'string' was found on type 'User'.
  });
}

解决方法:

  • 可以在正确的位置投射到 keyof typeof 使其工作:
ts 复制代码
const user = {
  name: "Daniel",
  age: 26,
};
 
const keys = Object.keys(user);
 
keys.forEach((key) => {
  console.log(user[key as keyof typeof user]);
});
  • 自定义类型谓词也可以通过内联缩小类型来工作。
ts 复制代码
function isKey<T extends object>(
  x: T,
  k: PropertyKey
): k is keyof T {
  return k in x;
}
 
keys.forEach((key) => {
  if (isKey(user, key)) {
    console.log(user[key]);
  }
});

更长的解释

这里有一个问题:使用Object.keys似乎并不像期望的那样工作。这是因为它没有返回你需要的类型。它不是一个包含所有键的类型,而是将其扩展为一个字符串数组。

ts 复制代码
const user = {
  name: "Daniel",
  age: 26,
};
 
const keys = Object.keys(user); // const keys: string[]

这意味着你不能使用键来访问对象上的值:

ts 复制代码
const nameKey = keys[0]; // const nameKey: string


user[nameKey];
// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ name: string; age: number; }'.
// No index signature with a parameter of type 'string' was found on type '{ name: string; age: number; }'.

TypeScript在这里返回字符串数组有一个很好的原因。TypeScript对象类型是开放式的。

在很多情况下,TS不能保证 Object.keys 返回的键实际上在对象上,因此将它们扩展为字符串是唯一合理的解决方案。查看此问题以了解更多详细信息。

在For循环中

如果你尝试做一个For...在循环中,你也会失败的。这是出于同样的原因------key被推断为字符串,就像 Object.keys 一样。

ts 复制代码
function printUser(user: User) {
    for (const key in user) {
        console.log(user[key]);
        // Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'User'.
        // No index signature with a parameter of type 'string' was found on type 'User'.
    }
}

但在许多情况下,你实际上能够确认对象是什么样的。所以,你是怎么做的?

解决方案1:转换为 keyof typeof

第一个选项是使用 keyof typeof 将键转换为更特定的类型。在下面的示例中,我们将 Object.keys 的结果转换为包含这些键的数组。

ts 复制代码
const user = {
  name: "Daniel",
  age: 26,
};
 
const keys = Object.keys(user) as Array<keyof typeof user>;
 
keys.forEach((key) => { 
    // (parameter) key: "name" | "age"
    // No more error!
  console.log(user[key]);
});

我们也可以在索引到对象的时候这样做。在这里, key 仍然是一个字符串类型-但在我们索引到user时,我们将其转换为 keyof typeof user

ts 复制代码
const keys = Object.keys(user);
 
keys.forEach((key) => {
  // (parameter) key: string
  console.log(user[key as keyof typeof user]);
});

然而,以任何形式使用 as 通常都是不安全的,这也不例外。

ts 复制代码
const user = {
  name: "Daniel",
  age: 26,
};
 
const nonExistentKey = "id" as keyof typeof user;
// const nonExistentKey: "name" | "age"
 
// No error!
const value = user[nonExistentKey];

对于这种情况, as 是一个相当强大的工具-正如你所看到的,它让我们对TypeScript撒谎。

解决方案2:类型谓词 Type Predicates

让我们来看看一些更聪明,可能更安全的解决方案。类型谓词怎么样?通过使用 isKey helper,我们可以在索引到对象之前检查键是否确实在对象上。我们通过在 isKey 的返回类型中使用 is 语法来正确推断TypeScript。

ts 复制代码
function isKey<T extends object>(
  x: T,
  k: PropertyKey
): k is keyof T {
  return k in x;
}
 
keys.forEach((key) => {
  if (isKey(user, key)) {
    console.log(user[key]);
  }
});

这个令人敬畏的解决方案来自Stefan Baumgartner关于这个主题的伟大博客文章TypeScript: Iterating over objects

解决方案3:泛型函数

让我们来看看一个稍微奇怪的解决方案。在泛型函数中,使用 in 操作符将类型缩小到键。我真的不知道为什么这里可以这样用,而非泛型版本不行。

ts 复制代码
function printEachKey<T extends object>(obj: T) {
  for (const key in obj) {
    console.log(obj[key]);  // const key: Extract<keyof T, string>
  }
}
 
// Each key gets printed!
printEachKey({
  name: "Daniel",
  age: 26,
});

解决方案4:在函数中包装Object.keys

另一种解决方案是将 Object.keys 包装在一个返回强制转换类型的函数中。

ts 复制代码
const objectKeys = <T extends object>(obj: T) => {
  return Object.keys(obj) as Array<keyof T>;
};
 
const keys = objectKeys({
  name: "Daniel",
  age: 26,
});
 
console.log(keys); // const keys: ("name" | "age")[]

这可能是最容易被滥用的解决方案------将转换操作隐藏在函数内部的方法会使其更具吸引力,但可能导致人们在使用时不加思考。

结论

我的首选解决方案?通常情况下,类型转换非常完美地完成了工作。它简单易懂,并且通常足够安全。

但是如果你喜欢类型谓词或通用解决方案的外观,请尽管使用。isKey函数看起来很有用,我会在下一个项目中借用它。

相关推荐
passerby606116 分钟前
完成前端时间处理的另一块版图
前端·github·web components
掘了23 分钟前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅26 分钟前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment1 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte2 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc