How to create a decorator that accepts an abstract class in Typescript

Viewed 234

I have a class decorator that adds a few properties to the class passed in.

type Class<T, TArgs extends any[] = any[]> = (...args: TArgs) => T;

export function myDecorator<T extends Class<any>>(Target: T) {
  const MyDecoratedClass = class extends Target {
    prop: string;
    fn(arg: string) {
      console.log(this.prop);
    }
  };

  const name = `Decorated${Target.name}`;
  Object.defineProperty(MyDecoratedClass, 'name', { value: name });

  return MyDecoratedClass as typeof MyDecoratedClass & T;
}

class MyClass extends myDecorator(SomeClass)<string> {
  foo: string;
  // ...
}

class MyImplementedClass extends myDecorator(SomeAbstractClass)<string> {
  foo: string;
  // ...
}

It works fine for non-abstract classes, but attempting to use it on abstract classes results in the error:

Cannot assign an abstract constructor type to a non-abstract constructor type. ts(2345)

This error message leads me to believe there must be a way to specify a type that accepts an abstract constructor type, otherwise it would (or should have) read something equivalent to Types cannot bind to abstract constructors.

I have a Class type:

type Class<T, TArgs extends any[] = any[]> = new(...args: TArgs) => T;

I thought maybe I could use the abstract keyword to achieve my goal:

type AbstractClass<T, TArgs extends any[] = any[]> = abstract new(...args: TArgs) => T;

// or

interface AbstractClass<T, TArgs extends any[] = any[]> {
  abstract new(...args: TArgs): T;
}

but no dice. The first results in a syntax error. The second results in

'abstract' modifier cannot appear on a type member. ts(1070)

Update

I found a case-specific workaround. This decorator is used specifically for React components. Therefore, I can create a base class, and extend that in my abstract class:

class DecoratedClass<P = {}, S = {}, SS = {}> extends myDecorator(Component)<P, S, SS> { }

abstract class MyClass extends DecoratedClass {
  // ...
}
0 Answers
Related