Search code examples
typescripttypesinterfaceextends

Typescript index signature class extends class


I try build block editor with Typescript. My problem is as follows...

I have this:

class A {...}

class B extends A {...}
class C extends A {...}
class D {...}

interface AConstructable {
  new (): A;
}
interface AElements {
  [name: string]: AConstructable
}
enter code here
const test: AElements = {
  blockB: B // ??? this takes B, but have type A :/ 
}

I need this:

const test: AElements = {
  blockB: B // ok!
  blockC: C // error
}

const x1 = new test['blockB'](...) // return B class instance

I tried different ways the above is closest but I don't know how to bite it. Is it possible?


Solution

  • In typescript anything which extends a type is assignable to that type. You require that the properties of AElements are each a constructor that returns an A. It is fine to return a B or a C because all Bs and Cs are also As.

    The only reason that you have an error on blockC is because of a missing comma. This code is actually fine:

    const test: AElements = {
      blockB: B,
      blockC: C
    }
    

    But that code will still show the value constructed by new test.blockB() as A because we haven't preserved any individual information about the properties.

    In order to keep the types of the individual property values while also enforcing that they extend A, you can create test through an identity function. Using a function allows allows to infer the exact types of the blockB and blockC and keep them, while also requiring that the object fits the index signature.

    const checkObject = <T extends {}>(obj: T & AElements): T & AElements => obj;
    
    const test = checkObject({
      blockB: B,
      blockC: C
    });
    
    const x1 = new test['blockB']() // return B class instance
    

    Typescript Playground Link