I'm having trouble understanding the following type definition:
type MergeHandlersReturnType<H extends Record<string, any>> = {
[K in keyof H]: ReturnType<H[K]>
}[keyof H]
Decomposing the above definition using similar types I'm not getting why the following is giving me compiler errors
interface R1 extends Record<string, any> {
"key1": () => 0|1
}
const v1: R1 = {
"key1": () => 1,
"key2": () => 2,
"key3": () => 3
}
type Merged<R1> = {
[K in keyof R1]: ReturnType<R1[K]>
^^^^^
}[keyof R1]
Type 'R1[K]' does not satisfy the constraint '(...args: any) => any'.
Type 'R1[keyof R1]' is not assignable to type '(...args: any) => any'.
Type 'R1[string] | R1[number] | R1[symbol]' is not assignable to type '(...args: any) => any'.
Type 'R1[string]' is not assignable to type '(...args: any) => any'.ts(2344)
I was expecting the Merged
type to be the unions of the return types of the keys of R1
but the indexing is failing.
CodePudding user response:
There are a couple of reasons why you are seeing a compiler error:
Merged<R1>
is stating thatR1
is a name of a generic type variable, not theR1
interface, henceR1
inMerged
is pretty much like genericT
which has no type definition and hypothetically has no properties that can be accessed viaT[K]
.After changing
Merged<R1>
toMerged
, becauseRecord
has a type ofany
ininterface R1 extends Record<string, any> {
, the resultingMerged
union type will always beany
...ReturnType
in[K in keyof R1]: ReturnType<R1[K]>
looks at what the second parameter ofRecord
is as it is the broader one and does not analyze the properties inside theR1
interface. To fix this, I would advise you to add a specific type instead of theany
like so:interface R1 extends Record<string,() => 0|1|2|3> {
. Ofcourse another way would be to get rid of the extends and define your keys with your types so you wouldn't have to define the union type manually and useMerged
like you want to:
interface R1 {
"key1": () => 0|1
"key2": () => 2,
"key3": () => 3
}
It really depends on your use case and hopefully above explains why you are seeing the error in your question.
Here is a link to the playground.
CodePudding user response:
The generic parameter R1
must extend Record<string, any>
for this to work, or its assumed to be any.
Or more precisely, it needs to extend a type that supports indexed signatures.
If you compare your definition of Merged<>
to the original you should be able to see that you are missing the constraint.
interface R1 extends Record<string, any> {
"key1": () => 0|1
}
const v1: R1 = {
"key1": () => 1,
"key2": () => 2,
"key3": () => 3
}
interface MyType {
[k: string]: any
}
type MergedForMyType<R1 extends MyType> = {
[K in keyof R1]: ReturnType<R1[K]>
} [keyof R1]
type MergedForRecordGeneric<R1 extends MyType | Record<string, any>> = {
[K in keyof R1]: ReturnType<R1[K]>
} [keyof R1]