[英]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.无论哪种方式都应该适合你。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.