What will be the best and (proper) approach to make this in TypeScript.
$Foo.getInstance('uid')
should return a FooInstance
based on implementations?
I want in the abstact class Entity
a method to get a instance from a pool,
for return the implemented EntityInstance
.
abstract class Entity {
abstract Instance: Partial<typeof EntityInstance>;
instances: { [uid: string]: EntityInstance } = {};
getInstance (uid: string ) {
return this.instances[uid]
}
}
abstract class EntityInstance {
prop='';
}
class Foo extends Entity {
Instance = FooInstance // @implementation
}
class FooInstance extends EntityInstance {
}
const $Foo = new Foo();
// need return InstanceType<FooInstance>
const instance = $Foo.getInstance('uid');
So example here:
const instance = $Foo.getInstance('uid')
should be a FooInstance
;
But it actually a EntityInstance
thats correct !
so i try change this method getInstance
to something like this.
getInstance <t=this>(uid: string ): InstanceType<this['Instance']> {
return this.instances[uid]
}
It working !:) but make some error type. Am noob with ts documentations, what change i can made to make this logic work fine. I know ts is powerful, but am not sure how to make this work fine for intelisence in my ide.
minimal reproductive demo typescript
i want myInstance.__foo2;
not produce error.
You could try changing the typings in Entity
to the following:
abstract class Entity {
abstract Instance: new () => EntityInstance;
instances: Record<string,
InstanceType<this["Instance"]> | undefined
> = {};
}
The Instance
property is a constructor that returns an EntityInstance
(or a subtype of it). And the instances
property type depends on the type of Instance
; by using polymorphic this
, we are saying for any subclass of Entity
the instances
property will depend on the type of Instance
in the same subclass.
This gives you the behavior you're looking for, at least for the example code:
class Foo extends Entity {
Instance = FooInstance;
}
class FooInstance extends EntityInstance {
__foo2 = 2;
}
const $Foo = new Foo();
$Foo.instances['myUid'] = new $Foo.Instance();
const myInstance = $Foo.instances['myUid'];
myInstance.__foo2; // okay
Note that polymorphic this
types can be a bit of a pain to work with inside the subclasses themselves:
class Foo extends Entity {
Instance = FooInstance;
constructor() {
super();
this.instances.abc = new FooInstance(); // error!
this.instances.abc = new this.Instance(); // error!
}
}
I'm not sure if you need to do something like this, but trying to set a property on this.instances
inside Foo
fails because the compiler doesn't know what this
will be if someone comes along and subclasses Foo
. It treats this
like an unspecified generic type and is not really able to verify that any particular value is assignable to it. In such cases you might need to use type assertions to suppress errors.
Another approach is to make Entity
a generic class where the type parameter T
corresponds to the particular subtype of EntityInstance
in subclasses:
abstract class Entity<T extends EntityInstance> {
abstract Instance: new () => T;
instances: Record<string, T | undefined> = {};
}
Any particular subclass needs to specify what T
should be (which is a bit redundant), but then everything works... both inside and outside the subclass:
class Foo extends Entity<FooInstance> {
Instance = FooInstance;
constructor() {
super();
this.instances.abc = new FooInstance(); // okay
this.instances.abc = new this.Instance(); // okay
}
}
myInstance.__foo2; // still okay