简体   繁体   English

如何在 TS 中键入未知对象数组?

[英]How to type array of unknown objects in TS?

I am trying to add types to function that takes array of objects and groups them by key.我正在尝试向 function 添加类型,它采用对象数组并按键对它们进行分组。

Here is my code:这是我的代码:

interface ITask {
  assignedTo: string;
  tasks: Array<string>,
  date: string;
  userId: number;
};

interface IFormattedOutput<T> {
  [prop: string]: Array<T>;
}

const testData: Array<ITask> = [
    {
      assignedTo: 'John Doe',
      tasks:['Code refactoring', 'Demo', 'daily'],
      date: '1-12-2022',
      userId: 123
    },
    {
      assignedTo: 'Maximillian Shwartz',
      tasks:['Onboarding','daily'],
      date: '1-12-2022',
      userId: 222
    },
    {
      assignedTo: 'John Doe',
      tasks:['Team Menthoring', 'daily', 'technical call'],
      date: '1-13-2022',
      userId: 123
    },
    {
      assignedTo: 'John Doe',
      tasks:['New dev onboardin','daily'],
      date: '1-12-2022',
      userId: 123
    }
]

const groupByKey = <T, K extends keyof T>(list: Array<T>, key: K):IFormattedOutput<T> => {
  return list.reduce((reducer, x) => {
    (reducer[x[key]] = reducer[x[key]] || []).push(x);
    return reducer;
  }, {});
};

const res = groupByKey <ITask, 'assignedTo'>(testData, 'assignedTo');

console.log(res);

Unfortunately o am getting TS error 'Type 'T[K]' cannot be used to index type '{}'.'不幸的是,我收到 TS 错误“Type 'T[K]' cannot be used to index type '{}'。”

Is there anything to do to fix this error?有什么办法可以解决这个错误吗?

2 problems: 2个问题:

  1. not typing the reducer seed properly, causing your current error没有正确输入减速器种子,导致您当前的错误

  2. even if you fix 1, you'd get a similar error, because you haven't restricted K to only point at string values of T, so the compiler will correctly warn you that it doesn't know if type T[K] can index IFormattedOutput<T>即使你修复 1,你也会得到类似的错误,因为你没有限制K只指向 T 的字符串值,所以编译器会正确地警告你它不知道类型T[K]是否可以索引IFormattedOutput<T>

ideal solution here will have the compiler throwing errors at you when you try to use this function improperly, so you need to define a type that only allows keys where T[K] is of type string, can do so like this:这里的理想解决方案是当您尝试不正确地使用此 function 时编译器向您抛出错误,因此您需要定义一个类型,该类型仅允许T[K]为字符串类型的键,可以这样做:

// this maps type T to only the keys where the key has a string value
type StringKeys<T> = { 
    [k in keyof T]: T[k] extends string ? k : never 
}[keyof T];

// this narrows type T to only it's properties that have string values
type OnlyString<T> = { [k in StringKeys<T>]: string };

then type your function like so:然后像这样输入您的 function:

// T extends OnlyString<T> tells compiler that T can be indexed by StringKeys<T>
// then you type key to be StringKeys<T> so compiler knows it must have a string value
// now the key must be a key of T that has a string value, so the index issues are solved
const groupByKey = <T extends OnlyString<T>>(list: T[], key: StringKeys<T>): IFormattedOutput<T> => {
  return list.reduce((reducer, x) => {
    (reducer[x[key]] = reducer[x[key]] || []).push(x);
    return reducer;
  }, {} as IFormattedOutput<T>); // also need to type reducer seed correctly
};

now the wrong input will throw type errors at you.现在错误的输入会向你抛出类型错误。

so these work as expected:所以这些按预期工作:

// note you don't need to declare the types, TS can infer them
groupByKey(testData, 'assignedTo');
groupByKey(testData, 'date');

but if you did:但如果你这样做了:

groupByKey(testData, 'tasks');
groupByKey(testData, 'userId');

you'd get compiler errors你会得到编译器错误

playground link 游乐场链接

There are 2 steps, I tested on my local and succeeded.有2个步骤,我在本地测试成功了。

First, I changed your interface IFormattedOutput<T> to this:首先,我将您的interface IFormattedOutput<T>更改为:

interface IFormattedOutput<T> {
  [key: string]: Array<T>;
}

The groupByKey function will be like as follow: groupByKey function 将如下所示:

// type guard for T[K] to be string otherwise throw an error
const isString = (propKey: any): propKey is string => typeof propKey === 'string';

const groupByKey = <T, K extends keyof T>(list: Array<T>, key: K): IFormattedOutput<T> =>
  list.reduce((reducer, x) => {
    const propKey = x[key];

    // this type narrowing is necessary to get rid of the
    // "type T[K] cannot be used to index type IFormattedOutput
    // due to type ambiguity.

    if (isString(propKey)) {
      (reducer[propKey] = reducer[propKey] || []).push(x);
    } else {
      throw new Error(`Expected string, got '${typeof propKey}'.`);
    }
    return reducer;

  }, {} as IFormattedOutput<T>);

I think Type guard and Typescript type narrowing are really important practices and seem to solve a lot of TS issues that I have come across.我认为类型保护Typescript 类型缩小是非常重要的实践,似乎解决了我遇到的很多 TS 问题。

Please note that even when this Typescript issue is solved, my eslint still has a warning because you are trying to reassign the reducer, so I silenced it with // eslint-disable-next-line no-param-reassign .请注意,即使解决了这个 Typescript 问题,我的 eslint 仍然会发出警告,因为您正在尝试重新分配减速器,所以我使用// eslint-disable-next-line no-param-reassign其静音。

Let me know if this solves your issue.如果这能解决您的问题,请告诉我。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM