美文网首页
在 TypeScript 中如何迭代对象的键

在 TypeScript 中如何迭代对象的键

作者: 涅槃快乐是金 | 来源:发表于2023-12-26 23:08 被阅读0次

原文:How to Iterate Over Object Keys in TypeScript
链接:https://www.totaltypescript.com/iterate-over-object-keys-in-typescript
作者:Matt Pocock

在 TypeScript 中迭代对象的键可能会比较困难。以下是我知道的所有解决方案。

简要说明

使用 Object.keys 进行迭代是行不通的,因为 Object.keys 返回的是一个字符串数组,而不是所有键的联合类型。这是有意设计的,不会更改。

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 的类型转换可以使其正常工作:
const user = {
  name: "Daniel",
  age: 26,
};
 
const keys = Object.keys(user);
 
keys.forEach((key) => {              
//(parameter) key: string
  console.log(user[key as keyof typeof user]);
});
  • 使用自定义类型断言也可以通过在内联中缩小类型。
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]);
                     //(parameter) key: "name" | "age"
  }
});

更详细的解释

Object.keys

这里的问题是:使用 Object.keys 似乎不像你期望的那样工作。这是因为它并不返回你需要的那种类型。

与其返回一个包含所有键的类型,它将其扩大为一个字符串数组。

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

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

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...in 循环

如果尝试使用 for...in 循环,你也会发现这种方法失败。原因相同 - 循环中的键被推断为字符串,就像 Object.keys 一样。

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 的结果转换为包含这些键的数组。

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

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

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

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:类型谓词

让我们看一些更智能、更安全的解决方案。使用类型推断怎么样?

通过使用 isKey 辅助函数,我们可以在索引之前检查键是否确实存在于对象中。

通过在 isKey 的返回类型中使用 is 语法,我们使 TypeScript 正确地进行类型推断。

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]);
                     //(parameter) key: "name" | "age"
  }
});

这个出色的解决方案来自于 Stefan Baumgartner 在这个主题上的博客文章

解决方案3:泛型函数

让我们看一个稍微奇怪的解决方案。在一个泛型函数内部,使用 in 运算符将缩小类型到键。

我实际上不确定为什么这个版本能够工作,而非泛型版本不能。

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 包装在一个函数中,该函数返回转换后的类型。

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 函数看起来足够有用,我可能会在我的下一个项目中借鉴它。

相关文章

  • TypeScript基础入门之迭代器和生成器

    转发 # TypeScript基础入门之迭代器和生成器 迭代性 如果对象具有Symbol.iterator属性的实...

  • 对象迭代与反迭代案例进阶

    案例课纲如下 如何实现可迭代对象和迭代器对象 如何使用生成器函数实现可迭代对象 如何进行反向迭代以及如何实现反向迭...

  • es6-迭代器和生成器

    迭代器 一个迭代器对象 ,知道如何每次访问集合中的一项, 并跟踪该序列中的当前位置。在 JavaScript 中...

  • 迭代器和生成器 (Iterator & Generator)

    迭代器 迭代器对象要求支持迭代器协议的对象,在Python中,支持迭代器协议就是实现对象的__iter__()和n...

  • Python高效编程(二)

    实际编程和面试都会遇到的典型问题。 如何实现可迭代对象和迭代器对象 如何使用生成器函数实现可迭代对象 如何进行反向...

  • python生成器迭代器

    在Python中,迭代器是遵循迭代协议的对象。使用iter()从任何序列对象中得到迭代器(如list, tuple...

  • 第三节:TypeScript对象类型

    对象类型 在 JavaScript 中,我们分组和传递数据的基本方式是通过对象。在 TypeScript 中,我们...

  • Python进阶 - 高性能计算之协程

    迭代器 可迭代对象 什么是可迭代对象 可迭代对象就是对象的类中实现了__iter__方法的对象。对于可迭代对象,可...

  • Python 迭代器与生成器

    迭代器 wiki百科:在 Python 中,迭代器是遵循迭代协议的对象。使用 iter() 从任何序列对象中得到迭...

  • TypeScript 对象类型 详解

    对象类型 在 JavaScript 中,最基础的分组和传递数据的方式就是使用对象。在 TypeScript 中,我...

网友评论

      本文标题:在 TypeScript 中如何迭代对象的键

      本文链接:https://www.haomeiwen.com/subject/qgqrndtx.html