Kelly
Kelly

Reputation: 41

derived generic class cannot be assigned to base generic class in typescript

I have a map storing all kinds of element. Defined as follow:

const map = new Map<string, IComponent<IBase>>();

I want to use this map to store instance of IComponent<IBase>, IComponent<INumber>, IComponent<IText> and so on.

The base interface is defined as follow:

// base interface
interface IBase {}
// base component type
export type IComponent<T extends IBase> = React.FC<T>;

Now I want to add a NumberComponent

// derived interface
interface INumber extends IBase {
    someNumber: number;
}
// some component
const NumberComponent: IComponent<INumber> = (props) => {
    return (
        <div>{props.someNumber}</div>
    );
}

I use map.set('number', NumberComponent) to store it to the map, but I got

Argument of type 'FC<INumber>' is not assignable to parameter of type 'FC<IBase>'.
  Types of parameters 'props' and 'props' are incompatible.
    Type 'PropsWithChildren<IBase>' is not assignable to type 'PropsWithChildren<INumber>'.
      Property 'someNumber' is missing in type 'PropsWithChildren<IBase>' but required in type 'INumber'.

I know I could use map.set('number', NumberComponent as IComponent<IBase>) to cast the type. However, I don't want to use as. Is there another way to solve this?

demo on ts playground

Upvotes: 1

Views: 79

Answers (1)

It is possible to do. You just need to overload your Map

import React from 'react'

interface IBase { }

export type IComponent<T extends IBase> = React.FC<T>;

interface INumber extends IBase {
  someNumber: number;
}


type MapOverloads = Map<string, IComponent<IBase>> & Map<string, IComponent<INumber>>
const map:MapOverloads  = new Map<string, IComponent<IBase>>();


const NumberComponent: IComponent<INumber> = (props) => {
  return (
    <div>{props.someNumber}</div>
  );
}

const BaseComponent: IComponent<IBase> = (props) => {
  return null
}

map.set('number', NumberComponent)

map.set('base', BaseComponent)

Playground

Main idea was shamelesly stolen from this answer of @jcalz

However, because map is mutable, and I assume you want it to be mutable, there is a problem with getter.


const expectNumber = map.get('number') // IComponent<IBase> | undefined

P.S. You was not able to assign IComponent<INumber> to IComponent<IBase> because of contravariance

Upvotes: 2

Related Questions