1

How can I make an array of subtypes, if the base class has a Generic parameter?

The following code illustrates what I'm trying to archive:

TS Playground

Full code:

type Type <T> = {
    new(...args: any[]): T
}

type Data = {
    [value: string]: any;
}

type Component<T extends Data> = {
    data: T;
}

component 1

type MyComponentData = {
    id: number;
}
class MyComponent implements Component<MyComponentData> {
    data = {
        id: 0
    }
}

component 2

type MyComponent2Data = {
    resource: string;
}
class MyComponent2 implements Component<MyComponent2Data> {
    data = {
        resource: '' 
    }
}

Attempt 1: id should be actually a number, but type is not checked

type ComponentRegistry = {
    label: string,
    component: Type<Component<Data>>,
    defaultData: () => Data
}

const components: ComponentRegistry[] = [
    {
        label: 'Example 1',
        component: MyComponent,
        defaultData: () => (
            {
                id: '123'
            }
        )
    },
    {
        label: 'Example 2',
        component: MyComponent2,
        defaultData: () => (
            {id: '123'}
        )
    }
]

Attempt 2: id should be actually a number, but type is not checked

type ComponentRegistry2<T> = {
    label: string,
    component: Type<Component<T>>,
    defaultData: () => T
}

const components2: ComponentRegistry2<Data>[] = [
    {
        label: 'Example 1',
        component: MyComponent,
        defaultData: () => (
            {
                id: '123' 
            }
        )
    },
    {
        label: 'Example 2',
        component: MyComponent2,
        defaultData: () => (
            {id: '123'}
        )
    }
]

Attempt 3

line 11: '{ id: number; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Data'

type extractGeneric<Type> = Type extends Component<infer X> ? X : never

type ComponentRegistry3 = {
    label: string,
    component<T extends Data>() : Type<Component<T>>,
    defaultData: () => extractGeneric<extractGeneric<ReturnType<ComponentRegistry3['component']>>>
}

const components3: ComponentRegistry3[] = [
    {
        label: 'Example 1',
        component: () => MyComponent,
        defaultData: () => (
            {
                id: '123' // Type 'string' is not assignable to type 'never'.
            }
        )
    }
]
  • 1
    @T.J.Crowder i've just added the code to the question, thanks! – Rafael de Oliveira Feb 04 '22 at 14:12
  • TypeScript lacks direct support for *existentially quantified generics* which is what you're looking for (e.g., `Array<<∃T>(Component)>` which is not valid TS syntax) so you'll need to work around it. I'm inclined to close this as a duplicate of [this question](https://stackoverflow.com/q/65129070/2887218); if you don't think it is, can you spell out the difference? – jcalz Feb 04 '22 at 14:31
  • I don't know how you plan to interact with an array of component registries if you don't know `T` for each one... what valid operations are there? Setting `new r.component().data = r.defaultData()` or something? Anyway, [this code](https://tsplay.dev/WKVpKm) is how I might approach this with the existential encoding from the other question. – jcalz Feb 04 '22 at 17:17
  • I've closed the question as a duplicate; if you believe this is not appropriate, please [edit] the question to clearly distinguish this from the topic of the other one and I can vote to reopen. Good luck! – jcalz Feb 04 '22 at 17:18
  • @jcalz it is a duplicate, thanks! – Rafael de Oliveira Feb 04 '22 at 18:13

0 Answers0