TypeScript 2.8 added a new core type InstanceType
which can be used to get the return type of a constructor function.
/**
* Obtain the return type of a constructor function type
*/
type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : any;
This feature is pretty nice, but falls apart when using abstract classes, which don't have a new
declaration according to TypeScript's type system.
At first, I thought I could get around this limitation by creating a similar but less-restrictive type (removing the extends new (...args: any[]) => any
guard):
export type InstanceofType<T> = T extends new(...args: any[]) => infer R ? R : any;
But it too falls apart when passed an abstract class, as it cannot infer the return type and defaults to any
. Here's an example using a mock DOM as an example, with attempted type casting.
abstract class DOMNode extends Object {
public static readonly TYPE: string;
constructor() { super(); }
public get type() {
return (this.constructor as typeof DOMNode).TYPE;
}
}
class DOMText extends DOMNode {
public static readonly TYPE = 'text';
constructor() { super(); }
}
abstract class DOMElement extends DOMNode {
public static readonly TYPE = 'text';
public static readonly TAGNAME: string;
constructor() { super(); }
public get tagname() {
return (this.constructor as typeof DOMElement).TAGNAME;
}
}
class DOMElementDiv extends DOMElement {
public static readonly TAGNAME = 'div';
constructor() { super(); }
}
class DOMElementCanvas extends DOMElement {
public static readonly TAGNAME = 'canvas';
constructor() { super(); }
}
// Create a collection, which also discards specific types.
const nodes = [
new DOMElementCanvas(),
new DOMText(),
new DOMElementDiv(),
new DOMText()
];
function castNode<C extends typeof DOMNode>(instance: DOMNode, Constructor: C): InstanceofType<C> | null {
if (instance.type !== Constructor.TYPE) {
return null;
}
return instance as InstanceofType<C>;
}
// Attempt to cast the first one to an element or null.
// This gets a type of any:
const element = castNode(nodes[0], DOMElement);
console.log(element);
Is there any way I can cast a variable to being an instance of the constructor that is passed, if that constructor is an abstract class?
NOTE: I'm trying to avoid using instanceof
because JavaScript's instaceof
is very problematic (2 different versions of the same module have different constructor instances).
You can query type of the prototype
of an abstract class
to obtain the type of its instances. This does not require that the type have a new
signature only that it has a prototype
property. Abstract classes do not have a new
signature but they do have a prototype
property.
Here is what it looks like
function castNode<C extends typeof DOMNode>(
instance: DOMNode,
Constructor: C
): C['prototype'] | null {
if (instance.type !== Constructor.TYPE) {
return null;
}
return instance;
}
The expression C['P']
in type position is called an indexed access type. It is the type of the value of the property named P
in the type C
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With