TypeScript。在对象上进行迭代

69 阅读4分钟

首页文章

TypeScript。在对象上进行迭代

Stefan Baumgartner

发表于2022年5月11日

作者:@ddprrt

阅读时间:7分钟

更多关于TypeScript的内容

在TypeScript中,很少有像试图通过遍历对象的键来访问对象属性这样的令人头疼的事情。这是一个在JavaScript中非常常见的模式,但TypeScript似乎会给你带来所有障碍。这个简单的行。

Object.keys(person).map(k => person[k])

TypeScript会向你抛出红色的方块字,而开发者则会翻转桌子。这实在是不好玩。对此,有几种解决方案。我曾经试图"改进 "Object.keys 。这是一个关于声明合并的很好的练习,但是......我不会经常这么做。另外,Dan在这方面写得太多了。注释绝对是一个解决方案。

但是,嘿,让我们先看看这个问题。

为什么在对象上迭代并不那么容易#

让我们来看看这个函数。

type Person = {  name: string,  age: number}function printPerson(p: Person) {  Object.keys(p).forEach((k) => {      console.log(k, p[k]) // ERROR!!  })}

我们想要的是通过访问键来打印一个Person'的字段。TypeScript不允许这样做。Object.keys(p) 返回一个string[] ,它太宽了,不允许访问一个非常明确的对象形状Person

但为什么会这样呢?我们只访问可用的键,这不是很明显吗?这就是使用Object.keys 的全部意义所在!

当然,但是我们也能够传递作为Person 的子类型的对象,这些对象有比Person 中定义的更多的属性。

const me = {  name: "Stefan",  age: 40,  website: "https://fettblog.eu"}printPerson(me); // All good!

所以,你可能会告诉我,printPerson 仍然应该正常工作。它打印了更多的属性,好吧,但它并没有破坏代码。它仍然是p 的键,所以每个属性都应该是可以访问的。

当然,但如果你不访问p 呢?

那么,让我们假设Object.keys 给你提供(keyof Person)[] 。就像我两年前的 "修复 "试图做的那样。你可以很容易地写出这样的东西。

function printPerson(p: Person) {  const you: Person = {    name: "Reader",    age: NaN  };  Object.keys(p).forEach((k) => {    console.log(k, you[k])  })  }const me = {  name: "Stefan",  age: 40,  website: "https://fettblog.eu"}printPerson(me);

如果Object.keys(p) 返回一个keyof Person[] 类型的数组,你也能访问Person 的其他对象。这可能是不加思索的。在我们的例子中,我们只是打印未定义。但如果你试图对这些值做什么呢。这将在运行时中断。

TypeScript防止你出现这样的情况。它很诚实,说。好吧,你认为它可能是keyof Person ,但实际上,它可能是这么多。

只有类型守卫可以帮助你。

function isKey<T>(x: T, k: PropertyKey): k is keyof T {  return k in x}function printPerson(p: Person) {  Object.keys(p).forEach((k) => {      if(isKey(p, k)) console.log(k, p[k]) // All fine!  })}

但是......不是那么好,不是吗?

for-in 循环#

还有一种方法可以迭代对象。

function printPerson(p: Person) {  for (let k in p) {    console.log(k, p[k]) // Error  }}

TypeScript也会给你同样的错误。Element隐含有一个'any'类型,因为'string'类型的表达式不能用来索引'Person'类型。出于同样的原因。你仍然可以做这样的事情。

function printPerson(p: Person) {  const you: Person = {    name: "Reader",    age: NaN  };  for (let k in p) {    console.log(k, you[k])  } }const me = {  name: "Stefan",  age: 40,  website: "https://fettblog.eu"}printPerson(me);

而且它将在运行时爆炸。

然而,像这样写,比起Object.keys 版本,你有一点优势。如果你添加一个泛型,TypeScript在这种情况下可以更加精确。

function printPerson<T extends Person>(p: T) {  for (let k in p) {    console.log(k, p[k]) // This works  }}

我们不要求pPerson (从而与Person 的所有子类型兼容),而是添加一个新的泛型参数T ,它从Person 延伸而来。这意味着所有与这个函数签名兼容的类型仍然是兼容的,但是当我们使用p 时,我们处理的是一个明确的子类型,而不是更广泛的超级类型Person

我们用与Person兼容的东西来替代T ,但TypeScript知道它的不同,足以防止你出错。

上面的代码是有效的。k 是属于keyof T 类型的。这就是为什么我们可以访问p ,它属于TTPerson 的一个子类型,这只是巧合。

但我们将不能做一些可能会破坏的事情,像这样。

function printPerson<T extends Person>(p: T) {  const you: Person = {    name: "Reader",    age: NaN  }  for (let k in p) {    console.log(k, you[k]) // ERROR  }}

我们不能用keyof T 来访问一个Person 。它们可能是不同的。真漂亮!

由于TPerson 的一个子类型,我们仍然可以分配属性。

p.age = you.age

很好!

底线#

TypeScript在这里对它的类型非常保守,这在一开始可能会显得很奇怪,但在你想不到的情况下会帮助你。我想这是JavaScript开发者通常会对编译器大喊大叫的部分,并认为他们在与它 "斗争",但是,嘿,也许TypeScript救了你的命。对于这种恼人的情况,TypeScript至少给了你变通的方法。

🐦有意见吗?需要帮助吗?发个微博吧!

☕️这有帮助吗?留下一个小提示对我帮助很大!

Cover of Front-End Tooling

我已经写了一本关于TypeScript的书!查看《TypeScript的50堂课》,由Smashing杂志出版。

TL/DR🏃♀️🚶♀️庆祝🎉🎉