Search code examples
typescriptecmascript-6mixinsflowtypehigher-order-components

type validation for higher order components


The question is basically is how to ensure type-checking with higher-order components implemented in a typical JavaScript way.

Hoc1 = (superclass) => class extends superclass { ... }
class A { ... }
class B extends Hoc1(A) { ... }

By type-checking I mean using any of two most prominent utilities: TypeScript or flow.

So far I have come up with the following snippet in TypeScript,

interface IAMixin {
  aMixedMethod(): void
}
interface IAMixinConstructor {
  new(): IAMixin
}

const AHoc: <T>(superclass: T) => T & IAMixinConstructor = (superclass) =>
  class extends superclass implements IAMixin {
    aMixedMethod() {}
}

class A {
  aMethod() {}
}
class B extends AHoc(A) {
  bMethod() {}
}

const b = new B();

b.aMixedMethod(); // false-positive: incrorrectly reports as missing method
b.aMethod();
b.bMethod();
b.cMethod(); // this is correctly caught, though

If I wrote the mixin this way

const AMixin: (superclass) => typeof superclass & IAMixinConstructor =
  (superclass) => class extends superclass implements IAMixin {
    aMixedMethod() {}
  }

then it thinks of superclass as any and false-negatively misses an error with the call of cMethod.

This seems to be possible at least in TypeScript, cause they have e.g. Object.assign correctly working for instances. But I need the construction of the same kind, but for classes.

Or do we need class types like Ruby classes?


Solution

  • what was missing was defining the AHoc parameter as of constructor function type for the class instead of an actual instance and the same for the returned value:

    interface IAMixin {
      aMixedMethod(): void
    }
    
    const AHoc: <T>(superclass: new () => T) => new () => (T & IAMixin) =
        (superclass) => class extends superclass implements IAMixin {
            aMixedMethod() { }
        }
    
    class A {
      aMethod() {}
    }
    class B extends AHoc(A) {
      bMethod() {}
    }
    
    const b = new B();
    
    b.aMixedMethod(); // now good
    b.aMethod();
    b.bMethod();
    b.cMethod(); // this is correctly caught