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.