Allow specifying interface implements clauses for the static side of classes
See original GitHub issueSearch Terms
class static side syntax interface type expression
Suggestion
Currently, you can only specify the static side interface of a class with a declaration. From the handbook:
const Clock: ClockConstructor = class Clock implements ClockInterface {
constructor(h: number, m: number) {}
tick() {
console.log("beep beep");
}
}
When I first wanted to do this (before looking at the docs), I tried to do it in this fashion:
class Clock: ClockConstructor implements ClockInterface {
...
}
And I was surprised to see that it didn’t work. My proposal is to make this a valid syntax as it’s more intuitive and understandable.
I believe that forcing class expressions conflicts with TypeScript’s design goals:
- Produce a language that is composable and easy to reason about.
Why use a class expression when there is no need for it? Why change your actual JavaScript logic for something that exists only in TypeScript and not in your production code.
Use Cases
Anywhere you need to set the interface of the static side of a class without having a need to specify it as an expression.
Examples
Take the example from the playground:
interface ClockConstructor {
new (hour: number, minute: number);
}
interface ClockInterface {
tick();
}
class Clock: ClockConstructor implements ClockInterface {
constructor(h: number, m: number) {}
tick() {
console.log("beep beep");
}
}
Checklist
My suggestion meets these guidelines:
- This wouldn’t be a breaking change in existing TypeScript/JavaScript code
- This wouldn’t change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn’t a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- This feature would agree with the rest of TypeScript’s Design Goals.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:88
- Comments:37 (3 by maintainers)
Top GitHub Comments
Ref #14600
We discussed #14600 at length and agreed that the syntax
static T
in theimplements
list of a class would be a reasonable place to allow this:Why not
static
members in interfaces?#14600 proposed the syntax
This is problematic for a couple reasons.
First, it seems to create a totally meaningless thing:
Second, there would be no way to access a static member through an interface type that declared it!
You would need to have some auxiliary keyword to turn an interface into the static version of itself:
Simply moving the
static
logic to the class syntax makes this substantially simpler, and (critically) allows classes to implement types that weren’t written using the hypotheticalinterface I { static property: T }
syntax.Why
implements static T
, notstatic implements T
?In the presumably-usual case of one instance and one static heritage member, this is substantially cleaner:
vs
Here’s my two cents:
I wish I could add static methods to my interfaces. The interfaces describe the API of an object. At the moment, that seems to be an instantiated class, however, when writing the interfaces and classes, we actually implement interfaces on classes, which are more akin to a messy mix of constructor and prorotype. A) Being able to write static methods inside a class alone is proof of that we cannot separate the two when working with class sugar.
From the docs:
B) I think that means that an interface is the description of the capabilities a class provides, which may include static methods - which we cannot declare with today’s interfaces.
So, if we use A) and B) as a line of thinking, there’s a mismatch. Which is why I would like to suggest a re-alignment between what classes provide and what interfaces provide - in the most intuitive way. Just let an interface look like a class declaration, with the class being the definition. It’s something which was possible way back in other languages (C++) as well, and helps have a clear contract.
Also, let’s not forget that it is valid code to pass a constructor as parameter, which allows us to do quite a bit of useful stuff and might benefits a lot from having an interface with static declarations. That’s also something which has to be included in such a contract.
There were some doubts about this suggestion, so let me address them:
Yes, if there are no non-static methods declared then
arg
has no members. If that situation makes sense should, however, not be part of a discussion about a language’s capabilities, since it leads to exactly this kind of highly hypothetical situation, which cannot be assessed at a language level. Maybe it does make sense for the logic? Maybe it increases the developer experience? No one can make any good argument without a concrete case, and then we are talking about a concrete case instead of the language, so let’s not talk about it here.arg.constructor.x
, which is valid code, and I needed it in several projects, which heavily make use of the type system to create a sound library usage (take a look at this project, for example)No, it’s not a legal call.
{}
does not implement the interface, so the type checker should reject it.fn
cannot accessarg.constructor.x
, which is valid JS and valid TS, so there would be an obvious bug in TS if the above was allowed.