I cannot for the life of me work out how to modify the return value of reduce without a number of clunky type assertions. 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>
Record<string, string | number>
as list
has the shape 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>
.
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.
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
string | number
. If you annotate list
as 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
string | number
. After this, any use of reduce()
with item.usage
would necessarily involve 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.
In this case I'd recommend leaving off the type annotation for list
and letting the compiler infer its type from the initialized value:
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
. This is a more useful classification of list
for your purposes.
Now you want to use reduce()
. The issue here is that you are passing in an initial accumulator which is an empty 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. (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
; and the type parameter in reduce()
is inferred to be Record<string, number>
, and so toMap
is also a Record<string, number>
.
Another way to do it is to manually specify the type parameter when you call 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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.