简体   繁体   English

Typescript 将对象列表减少为具有不同形状的 object

[英]Typescript reduce a list of objects to an object with a different shape

I cannot for the life of me work out how to modify the return value of reduce without a number of clunky type assertions.如果没有一些笨拙的类型断言,我这辈子都无法弄清楚如何修改 reduce 的返回值。 For example:例如:

const list: Array<Record<string, string | number>> = [
  {
    resourceName: "a",
    usage: 20
  },
  {
    resourceName: "b",
    usage: 50
  }
];

const toMap = list.reduce(
  (acc, item) => ({
    ...acc,
    [item.resourceName]: item.usage
  }),
  {}
);

toMap has the type Record<string, string | number> toMap的类型为Record<string, string | number> Record<string, string | number> as list has the shape Array<Record<string, string | number>> Record<string, string | number>作为list的形状为Array<Record<string, string | number>> Array<Record<string, string | number>> but I don't care about the shape of my input list, I care about the shape of my output object, which is Record<string, number> . Array<Record<string, string | number>>但我不关心我的输入列表的形状,我关心我的 output object 的形状,即Record<string, number>

In this example I could add a type assertion to the end to get the right type but there are times when the list type and output type don't match and having to use as unknown as X for an operation as common as reduce seems incorrect to say the least.在这个例子中,我可以在末尾添加一个类型断言以获得正确的类型,但有时list类型和 output 类型不匹配,并且必须使用as unknown as X来进行像reduce这样常见的操作似乎不正确至少可以说。

You do need to care about the shape of the input list, at least to the extent that you want the compiler to understand that item.usage is a number and not a string | number您确实需要关心输入列表的形状,至少在您希望编译器理解item.usage是一个number而不是string | number的范围内。 string | number . string | number If you annotate list as Array<Record<string, string | number>>如果将list 注释Array<Record<string, string | number>> Array<Record<string, string | number>> , then you're explicitly telling the compiler to forget everything about list except that it's an array of objects whose properties are of type string | number Array<Record<string, string | number>> ,然后你明确告诉编译器忘记关于list的所有内容,除了它是一个属性类型为string | number的对象数组。 string | number . string | number After this, any use of reduce() with item.usage would necessarily involve string | number在此之后, reduce()item.usage的任何使用都必然涉及string | number string | number , unless you employ something like a type assertion to give the compiler back some of the information you threw away earlier. string | number ,除非您使用类型断言之类的东西来向编译器返回您之前丢弃的一些信息。

In this case I'd recommend leaving off the type annotation for list and letting the compiler infer its type from the initialized value:在这种情况下,我建议不要使用list的类型注释,让编译器从初始化值推断其类型:

const list = [
  {
    resourceName: "a",
    usage: 20
  },
  {
    resourceName: "b",
    usage: 50
  }
];

/* const list: {
    resourceName: string;
    usage: number;
}[] */

You can see that this inferred type is an array of objects whose resourceName property is of type string and whose usage property is of type number .您可以看到,这个推断的类型是一个对象数组,其resourceName属性为string类型,而usage属性为number类型。 This is a more useful classification of list for your purposes.对于您的目的,这是一个更有用的list分类。


Now you want to use reduce() .现在你想使用reduce() The issue here is that you are passing in an initial accumulator which is an empty object {} .这里的问题是您传入了一个初始累加器,它是一个空的 object {} Left to its own devices, the compiler will infer {} as the type of this initial accumulator, and from there infer that the generic type parameter on the reduce() call signature is also of type {} , and finally conclude that the return value is of type {} as well.留给它自己的设备,编译器将推断{}作为这个初始累加器的类型,并由此推断reduce()调用签名上的泛型类型参数也是{}类型,最后得出返回值也是{}类型。 (Sometimes the compiler's type inference gives you the types you want, and other times it doesn't. That's just the way it goes with heuristics.) If you want something else to happen, you need to prevent this inference. (有时编译器的类型推断会为您提供所需的类型,而有时则不会。这就是启发式算法的方式。)如果您希望发生其他事情,则需要防止这种推断。

One way to do it is to declare the initial accumulator ahead of time with its type explicitly annotated to what you want:一种方法是提前声明初始累加器,其类型显式注释为您想要的:

const init: Record<string, number> = {};
const toMap = list.reduce(
  (acc, item) => ({
    ...acc,
    [item.resourceName]: item.usage
  }),
  init
);
// const toMap: Record<string, number>

Now the compiler does not need to infer the type of init ;现在编译器不需要推断init的类型; and the type parameter in reduce() is inferred to be Record<string, number> , and so toMap is also a Record<string, number> .并且reduce()中的类型参数被推断为Record<string, number> ,因此toMap也是Record<string, number>

Another way to do it is to manually specify the type parameter when you call reduce() .另一种方法是在调用reduce()时手动指定类型参数。 This prevents the inference of the type parameter, and allows the compiler to contextually infer the type of your initial accumulator from it:这可以防止类型参数的推断,并允许编译器根据上下文推断初始累加器的类型:

const toMap = list.reduce<Record<string, number>>(
  (acc, item) => ({
    ...acc,
    [item.resourceName]: item.usage
  }),
  {}
);
// const toMap: Record<string, number>

Either way should work for you.无论哪种方式都应该适合你。

Playground link to code Playground 代码链接

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

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