Home > Mobile >  Typescript: Use a class with Mixins as a type
Typescript: Use a class with Mixins as a type

Time:02-15

I'm trying to use Typescript Mixins to compose my class instead of duplicating behaviors (like the getImage function below).

I followed those instructions https://www.typescriptlang.org/docs/handbook/mixins.html and it worked well.

But there's a problem I can't solve is that now I can't use my classes with mixins as a type anymore (see the functions at the end).

Do you know how I could get around this?

Thanks


// A - Some Mixins

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

function withStoredImages(Base: Constructor) {
  return class extends Base {
    storageToken?: string;

    getImage(formatSlug: string) {
      if (!this.storageToken) return null;
      return `${formatSlug}${this.storageToken}.png`;
    }
  };
}

// B - The actual class I want to create

class BaseEntity {
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

export const Entity = withStoredImages(BaseEntity);

const myEntity = new Entity('1', 'Bobby');

// Works
console.log(myEntity.storageToken);

// Works
console.log(myEntity.getImage('original'));

// Good. We can use BaseEntity as a type for our arg
export function printBaseEntity(entity: BaseEntity) {
  // Works
  console.log(entity.name);

  // Error: "Property 'storageToken' does not exist on type 'BaseEntity'."
  // Makes sense, since it's before we apply the withStoredImages mixin
  console.log(entity.storageToken);
}

// Error: "'Entity' refers to a value, but is being used as a type here. Did you mean 'typeof Entity'?"
// Why can't I use "Entity" as a type for my arg? Let's try using "typeof Entity" then...
export function printEntity(entity: Entity) {
  console.log(entity.name);
}

export function printEntity2(entity: typeof Entity) {
  // ... Error: "Property 'storageToken' does not exist on type 'typeof (Anonymous class)'?"
  console.log(entity.storageToken);
}

Stackblitz: https://stackblitz.com/edit/typescript-rbk3fn?file=entity.model.ts

CodePudding user response:

// Error: "'Entity' refers to a value, but is being used as a type here. Did you mean 'typeof Entity'?"
export function printEntity(entity: Entity) {
  console.log(entity.name);
}

Above error makes sense because Entity is not a literal class, it refers to const.

Consider this simplified example:

const foo = class { }

type Foo = foo // error

You are allowed to use class in a type scope only if it was declared as a class declaration and not expression.

In this case you should use typeof Entity. However. typeof Entity represents type of constructor. It means that you are not allowed to use storageToken property, because typeof Entity is a constructor function. You should call it with new. Consider this example:

export function printEntity2(entity: typeof Entity) {
  const x = new entity()
  x.getImage('hello') // ok
  x.storageToken // ok
  console.log(x.storageToken); // ok
}

Now it works as expected

CodePudding user response:

One way, though clumsy, is to declare a new type this way

type EntityType = BaseEntity & { storageToken?: string; getImage(formatSlug: string): unknown; };

This type now can be used as parameter of methods printEntity and printEntity2.

 function printEntity(entity: EntityType) {
   console.log(entity.name);
 }

  function printEntity2(entity: EntityType) {
   console.log(entity.storageToken);
 }

However EntityType is not callable, so you can't call new EntityType(...). You still need to create instances with the Entity constructor: const myEntity = new Entity('1', 'Bobby') as EntityType;.

So, an incomplete / clumsy solution... I wonder if both types could be combined together in an elegant way.

  • Related