Naming in computer science is notoriously difficult.
In TypeScript, we generally use the same name to refer to the constructor of a class (which is a value that exists at runtime), and as the type of the instances of the class (which only exists at design time and is not emitted to JavaScript).
So in
class Foo { }
const foo: Foo = new Foo();
there is a class constructor named Foo
, which can be used to construct class instances whose type we call Foo
.
This is a bit like using the term “Lego” to refer both to the company that manufactures plastic construction toys, and to the construction toys themselves. Or like “Toyota” as the name of a company and the name of the vehicles that they make. We say “Toyota makes and sells Toyota vehicles” or “Lego makes and sells Lego construction toys” without misunderstanding, but the analogous “Foo
constructs Foo
instances” might be confusing. So be it, I guess.
When we talk about a class, we might be talking about its static aspects, which pertain to the constructor (generally singular, “the constructor”, because there’s just one of them, which is the same for all instances), or we might be talking be talking about its instance aspects, which pertain to the instances of the class (generally plural, “instances“, because the single constructor can make many instances, each of which has possibly different state or properties from others).
From the type system’s standpoint, the type Foo
corresponds to the shape of the instance side. To get the type of the static side, we can use the typeof
type query operator operating on the constructor value named Foo
: typeof Foo
. So, Foo
is the instance side type and typeof Foo
is the static side type.
Let’s augment Foo
to show how this works:
class Foo {
instanceProp: string;
constructor(constructorArgument: string) {
this.instanceProp = constructorArgument;
}
instanceMethod(): void {
console.log("Instance method called on " + this.instanceProp)
}
static staticProp: string = "Static Thing";
static staticMethod(): void {
console.log("Static method called on " + this.staticProp)
}
}
That’s a single class definition, but it has a static side and an instance side. The static side of Foo
, that is, things pertaining to its constructor, includes:
- the constructor itself, whose signature is
{new(constructorArgument: string): Foo}
- the
staticProp
property, whose type isstring
, and - the
staticMethod
method, whose signature is{(): void}
.
This type is called typeof Foo
, but you can actually define your own interface for it if you want, like so:
interface StaticSideOfFoo {
new(constructorArgument: string): Foo;
staticProp: string;
staticMethod(): void;
}
The instance side of Foo
, that is, things pertaining to its instances, includes:
- the
instanceProp
property, whose type isstring
, and - the
instanceMethod
method, whose signature is{(): void}
.
This type is called Foo
, and you can define your own interface for this also:
interface InstanceSideOfFoo {
instanceProp: string;
instanceMethod(): void;
}
Let’s make sure we understand the difference:
const foo1 = new Foo("Number 1");
console.log(foo1.instanceProp); // Number 1
foo1.instanceMethod(); // Instance method called on Number 1
const foo2 = new Foo("Number 2");
console.log(foo2.instanceProp); // Number 2
foo2.instanceMethod(); // Instance method called on Number 2
console.log(Foo.staticProp); // Static Thing
Foo.staticMethod(); // Static method called on Static Thing
Note that foo1
and foo2
have no direct access to the constructor signature or to staticProp
or staticMethod
:
new foo1("oops"); // error!
foo1.staticProp; // error!
foo1.staticMethod(); // error!
and that Foo
has no direct access to instanceProp
or instanceMethod
:
Foo.instanceProp; // error!
Foo.instanceMethod(); // error!
And the values foo1
and foo2
have type Foo
, which is similar to InstanceSideOfFoo
, whereas the value Foo
has type typeof Foo
, which is similar to StaticSideOfFoo
. We can verify that here:
const instanceSideOfFoo: InstanceSideOfFoo = foo1; // okay
const instanceSideOfFooOops: InstanceSideOfFoo = Foo; // error!
const staticSideOfFoo: StaticSideOfFoo = Foo; // okay
const staticSideOfFooOops: StaticSideOfFoo = foo1; // error!
It helps to be able to talk about these different sides because they have different shapes and different uses. If you want to make a function that accepts the Foo
constructor, you want to talk about typeof Foo
or StaticSideOfFoo
or new (x: string)=>Foo
, whereas if you want to make a function that accepts an instance of Foo
, you want to talk about Foo
or InstanceSideOfFoo
or {instanceProp: string}
, etc.
Without such a distinction you might end up trying to buy the Lego corporation for your children or becoming a shareholder in a 1997 Toyota Camry. Or maybe not, but the analogous mistakes in TypeScript would be too easy to make. What can I say, naming things is tough.
Okay, hope that helps; good luck!