0

I encounter a puzzling problem when composing a complexe class hierarchy, each class being exported from separate ES6-modules-like files, with mixins involved.

Here's a simplified exemple reproducing my problem:

File "mixinA.ts":

type Constructor = new (...args: any[]) => {};

export default function addMixinA<TBase extends Constructor>(Base: TBase) {
    return class MixedInA extends Base {
        public prop1 = 1
    }
}

File "classB.ts":

import addMixinA from "./mixinA"

class ClassB {
    public prop2 = 2
}

let ClassB_WithMixin = addMixinA(ClassB)
export default ClassB_WithMixin

File "classC.ts":

import classB from "./classB"

class ClassC {

    // 'classB' refers to a value, but is being used as a type here. Did you mean 'typeof classB'? ts(2749)
    public classB_instance: classB
}

As you can see in the comment I added above the faulting line, an error is produced in the file classC.ts when using the export from classA.ts.

Am I doing something wrong? Can you spot my error?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Colisan
  • 193
  • 1
  • 9

2 Answers2

0

Congratulations on your first post!

Try this:

class ClassC {
    public classB_instance: InstanceType<typeof classB> = new classB()
}

You cannot annotate a field by using classB, because classB is not a type. It's a value. To use the type of this value, the typeof operator is needed (type Foo = typeof classB).

Alternatively, do this:

class ClassB_WithMixin extends addMixinA(ClassB) {}

class ClassC {

    public classB_instance: ClassB_WithMixin = new ClassB_WithMixin()
}

This difference here is that instead of doing assignment (let ClassB_WithMixin = addMixinA(ClassB)), we're doing inheritance (class ClassB_WithMixin extends addMixinA(ClassB) {}) which produces a real class. And classes in TypeScript are both types and values.

Karol Majewski
  • 23,596
  • 8
  • 44
  • 53
  • Thanks a lot! I'll take a spin on your alternative solution and change my export in the file "classB.ts" to `export default class ClassB_WithMixin extends addMixinA(ClassB) {}`. There's a extraneous class in here, but the code seems more clean to me =) – Colisan Dec 03 '20 at 22:30
0

Yeah, TypeScript mixins would be valuable if they could work out the rough edges. As it is, I think that I would prefer to break the DRY principle rather than introduce this confusion.

Using the example from the OP, addMixinA is supposed to be returning a class, which it does but, as a return value, that class is treated as a value. So, unlike a normally declared class, you can't also use it directly as a type.

It is all well and good when you both do the mixin and use the class within the same file. But, if you want to configure the class in one file and export it to be used in another file as per "classB.ts", and you want to both use that class to create new instances and define properties to be of that type, you have to do something like -

export const ClassB_WithMixin_Class = addMixinA(ClassB);
export type ClassB_WithMixin_Type = InstanceType<typeof ClassB_WithMixin_Class >;

Way too messy for my liking.

martinp999
  • 419
  • 6
  • 10