There is this Q&A about Abstract constructor types in TypeScript, which is a question I would like to know the answer to:
Abstract constructor type in TypeScript
Unfortunately, the accepted answer is little more that an unexplained one-liner: type Constructor<T> = Function & { prototype: T }
It appears that this answer is good enough for the asker, it is the accepted answer, but I'm unable to understand how the answer applies to the question.
I've tried asking in the comments, and I've tried asking someone in chat to explain the answer, but to no avail. Also, I recognize that this is only barely a different question from that one, but this is still a unique question about code.
How can I use a type of Function & { prototype: T }
to emulate an abstract class of known constructor type?
In JavaScript, all functions have a special property called prototype
. If you use a function as a constructor (by calling it with new
), the constructed instance will have that as its prototype. This is how classes worked in JavaScript before ES2015:
// ES5-flavored class
function Foo() { } // a normal function
Foo.prototype.prop = 0; // adding a property to Foo.prototype
var f = new Foo(); // using Foo as a constructor
f.prop; // the instance inherits the added property
ES2015 introduced the class
syntax, which behaves the same way under the hood... and so constructor objects still have a prototype
property from which constructed instances inherit.
// ES2015-flavored class
class Foo { } // explicitly a class
Foo.prototype.prop = 0; // adding a property to Foo.prototype
const f = new Foo(); // using Foo as a constructor
f.prop; // the instance inherits the added property
TypeScript models this by declaring that the Function
interface, which all functions implement, has a prototype
property of type any
. Furthermore, when the class
syntax is used, the constructor is still seen as a Function
, and the prototype
property of the constructor is narrowed from any
to the same type as the constructed class instance:
// TypeScript code
class Foo {
prop!: number; // Foo has a prop
}
Foo.prototype; // inspects as type Foo
Foo.prototype.prop = 0; // so this works
const f = new Foo(); // inspects as type Foo
f.prop; // inspects as type number
Even abstract
classes in TypeScript have a prototype
property whose type is the same as the instance type. So while you can't call new
on an abstract class constructor, or match it to a newable type like new() => any
, you can still talk about its prototype
:
// more TS code
abstract class Bar {
abstract prop: number;
}
Bar.prototype; // inspects as type Bar
Bar.prototype.prop = 0; // so this works
const b = new Bar(); // whoops can't do this though
All this means that in TypeScript a possibly-abstract class constructor is (a subtype of) a Function
whose prototype
property is the instance type for the constructor. Thus, you can say that
type PossiblyAbstractConstructor<T> = Function & {prototype: T};
using the intersection operator to combine Function
and "object with a prototype
property of type T
"...
or similarly
interface PossiblyAbstractConstructor<T> extends Function {
prototype: T;
}
using interface extension to achieve the same effect...
and you know that the following is valid:
const fooConstructor: PossiblyAbstractConstructor<Foo> = Foo;
const barConstructor: PossiblyAbstractConstructor<Bar> = Bar;
That should explain how the original question's answer is applicable. Hope that helps. Good luck!
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