如何迭代 TypeScript 中的已知属性键?

Posted

技术标签:

【中文标题】如何迭代 TypeScript 中的已知属性键?【英文标题】:How to iterate over known property keys in TypeScript? 【发布时间】:2022-01-04 05:34:25 【问题描述】:

这是我的代码:

interface Siswa 
  name: string,
  points:  a: number, b: number, c: number, d?: number, e?: number 


function find_average_points(data: Array<Siswa>): Array<name: string, average: number> 
  let returned_array: Array<name: string, average: number> = [];
  data.forEach((item: Siswa) => 
    let sum: number = 0;
    let keys = Object.keys(item.points);
    keys.forEach((key: any) => sum+=item.points[key]);
    returned_array.push(name: item.name, average: (sum/keys.length));
  );
  return returned_array;

当我在 javascript 中尝试这个函数时,它运行正确并且结果是我想要的,但是在 TypeScript 中,我在item.points[key] 中遇到了错误。它说:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type ' a: number; b: number; c: number; d?: number | undefined; e?: number | undefined; '.
  No index signature with a parameter of type 'string' was found on type ' a: number; b: number; c: number; d?: number | undefined; e?: number | undefined; '

我不知道这是什么意思。

【问题讨论】:

这是一个错误,因为objects may have more keys than are declared in their interfaces,所以除非您想在sum 中添加可能随机的东西,否则您应该使用硬编码的键数组并过滤它们like this。如果这满足您的需求,我很乐意写一个答案来解释这一点(明天因为现在是我的就寝时间);如果没有,请告诉我哪些用例不满意。 【参考方案1】:

使用Object.keys() 返回类型string[](如this comment 中所述)。相反,您可以为 points 对象中的已知键创建数组,使用它们来构建您的类型,然后在对这些值求和时循环遍历这些键:

TS Playground link

const definitePoints = ['a', 'b', 'c'] as const;
const maybePoints = ['d', 'e'] as const;

type Points = (
  Record<typeof definitePoints[number], number>
  & Partial<Record<typeof maybePoints[number], number>>
);

interface Siswa 
  name: string,
  points: Points;


interface SiswaAverage 
  name: string;
  average: number;


function find_average_points (data: Array<Siswa>): Array<SiswaAverage> 
  const result: Array<SiswaAverage> = [];

  for (const name, points of data) 
    let sum = 0;
    let count = 0;

    for (const point of [...definitePoints, ...maybePoints]) 
      const value = points[point];
      if (typeof value !== 'number') continue;
      sum += value;
      count += 1;
    

    result.push(name, average: sum / count);
  

  return result;

【讨论】:

【参考方案2】:

您可以结合使用 keyof 和 null 检查来解决问题。

    interface Points  a: number, b: number, c: number, d?: number, e?: number  // only did to use keyof

interface Siswa 
  name: string,
  points: Points


function find_average_points(data: Array<Siswa>): Array<name: string, average: number> 
  let returned_array: Array<name: string, average: number> = [];
  data.forEach((item: Siswa) => 
    let sum: number = 0;
    let keys = Object.keys(item.points) as (keyof Points)[]; // use this if you know you wont add extra non number properties in Point 

    keys.forEach((key) => sum+=item.points[key] ?? 0); // for d and e with are optional
    returned_array.push(name: item.name, average: (sum/keys.length));
  );
  return returned_array;

let obj: Siswa =  name: 'Paris', points:  a: 2 , b:2, c: 4, d: 4  // can not add random stuff type safe

console.log(find_average_points([obj]))

【讨论】:

由于您在此答案中使用了我的评论中的whatsit 代码,您能解释一下您的处理方法是什么吗? Object.keys() 返回 string[] on purpose。通过编写Object.keys(item.points) as (keyof Point)[],您已经对编译器撒谎Object.keys(item.points) 包含的内容。您可能会争辩说,像whatsit 这样具有额外属性的情况在现实中不太可能发生,因此您在实践中不必担心它,但您可能应该对此说一些。特别是如果您在代码中包含问题案例。 @jcalz 老实说,在写这篇文章之前我什至没有读过你的评论,因为这是最明显的解决方案,我甚至没有对编译器撒过谎,声明为 '( keyof Point)[]' 设置了“let keys: (keyof Point)[]”的类型,这一直是我的意图。 那么为什么你的答案中有whatsit,你的处理方法是什么? NaN 出来的事实是个问题,对吧?因为sum += items.points[key] ?? 0 仍然可以最终对各种非数字值执行+=(可以使用go crazy)。你评论了// wrong data type if const whatsit: Siswa;这应该作为解释或缓解吗?如果是这样,您可以在答案中使用单词来解释吗?就目前而言,// defined what it really is 的评论并不准确。帮帮我! 哦,现在我明白了你在说什么,我很抱歉我可能混淆了你的类型脚本操场代码的问题。我会用我回来的原始问题代码更新它。 更新了原始问题:@jcalz 您创建了any 类型的whatit,这就是为什么您能够在“d”和“e”中输入错误值的原因,这不是真实的情况,这就是为什么你的平均值是“NAN”,如果它只是类型化whatit:Siswa,这也是不可能的

以上是关于如何迭代 TypeScript 中的已知属性键?的主要内容,如果未能解决你的问题,请参考以下文章

Typescript - 如何声明具有已知属性的匿名对象?

如何通过 TypeScript 中的映射类型删除属性

如何获取与 Typescript 中的接口匹配的对象的所有属性? [复制]

在 TypeScript 中,当类型是函数的参数时,有没有办法限制 Partial<T> 类型的额外/多余属性?

Typescript 根据同一对象中的属性推断函数的对象键

如何迭代由json对象中的键访问的数组[重复]