Home > OS >  Trouble understanding type indexing
Trouble understanding type indexing

Time:05-05

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:

  1. Merged<R1> is stating that R1 is a name of a generic type variable, not the R1 interface, hence R1 in Merged is pretty much like generic T which has no type definition and hypothetically has no properties that can be accessed via T[K].

  2. After changing Merged<R1> to Merged, because Record has a type of any in interface R1 extends Record<string, any> {, the resulting Merged union type will always be any... ReturnType in [K in keyof R1]: ReturnType<R1[K]> looks at what the second parameter of Record is as it is the broader one and does not analyze the properties inside the R1 interface. To fix this, I would advise you to add a specific type instead of the any 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 use Merged 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]
  • Related