Add a new type of class declaration to support mixins
🔍 Search Terms
"mixin", "InstanceType", "typeof", "class"
✅ Viability Checklist
- [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
- [x] This wouldn't change the runtime behavior of existing JavaScript code
- [x] This could be implemented without emitting different JS based on the types of the expressions
- [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- [x] This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- [x] This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ Suggestion
When I run a mixin function, it outputs just the value of the returned expression, not the type
function myMixin<T extends new (...args: any[]) => any>(ctor: T) {
return class extends ctor {
a = 1;
}
}
class MyClass {
b = 2
}
const Result = myMixin(MyClass);
// ↓ Result refers to a value, but is being used as a type here. Did you mean typeof Result ? ↓
const istance: Result = new Result();
So I would need to define the type too
// ...
const Result = myMixin(MyClass);
type Result = InstanceType<typeof Result>;
// ...
I suggest adding a new syntax to define both a variable and a type with the same name
// ...
const Result = myMixin(MyClass) as type; // "as class" is also a viable option
// ...
Examples:
namespace Something {
export class Idk {
c = 1;
}
}
const a = Something.Idk as type; // Ok
function someFunc() {
return class {
e = 1;
}
}
const b = someFunc() as type; // Ok
const c = 1 as type; // Error: Expression "1" doesn't have any type attached
⭐ OPTIONAL extra generalised suggestion
Additionally, it would be nice if we could be able to generalise this process for all variables that also have an attached type
const a = 1;
type a = 2; // The type is unrelated to the value, no usage of this comes to my mind at the moment, but it already works
const b = a;
type b = a;
// Or
const b = a as type; // Defines a value from the `a` variable and a type from the `a` type
But it wouldn't work out of the box for the main suggestion, since Result doesn't actually have any type attached it would require functions (myMixin() for example) to be able to have attached return types
📃 Motivating Example
(Mentioned on the suggestion body)
💻 Use Cases
What do you want to use this for?
To improve the development of mixins in general
What shortcomings exist with current approaches?
Too verbose and feels like something it should to by default
const Result = myMixin(MyClass);
type Result = InstanceType<typeof Result>;
Creates an actual runtime class, that surely adds an overhead, althought it's minimal
class Result extends myMixin(MyClass) { }
What workarounds are you using in the meantime?
The runtime class one
As written this is a runtime feature and will be declined. (You want class X = f() to be transformed into const X = f() at runtime)
You might want to consider changing the request so that the runtime portion would just need type erasure, like maybe const X = f() as class? Or something like that.
I think I’ve seen such requests before though. Have to search for it to see if I find a duplicate. Relevant results:
#18942 asks for this to happen automatically for all class constructor values, but this was declined. Also #18967. Also #45013. This isn't a duplicate of those because it looks like you're asking for this to be opt-in behavior instead of automatically.
Sorry for that, I didn't realise you guys meant that by "runtime feature", but I get how this is a problem since you can't just erase the TypeScript specific syntax when compiling. As an alternative syntax I think it would be better to do something like this
const a = 1;
type a = 2;
// ↓ "type" instead of "class", so that it would make sense even in more generic cases like this one that doesn't involve classes at all
const b = a as type;
I think that making this thing opt-in is quite important as it would probably do a lot of unwanted things otherwise
(I'm not "you guys" exactly, just an interested bystander.)
Wait, why are you expanding the request to non-constructors? What are you expecting the type b to be there? 1? 2? Something else? For constructors it's obvious (ish) that the type would be the instance type of the constructor. I'd suggest keeping this request as just about class constructors, so that you don't have to lay out a whole proposal for all possible values.
The proposal is mainly about class constructors, but I think it could be expanded to every identifier that both stores a value and a type.
I wanted to make the proposal as generalised as possible, the specific use case of mixins may not be enough to justify this feature.
In that case the value of b would be 1 and the type would be 2
const b = a as type;
// Same as
const b = a;
type b = a;
The main problem would be allowing a function to return a type attached to its return value, but this problem seems to be minimal enough to not evem be mentioned in this reply addressing some of the issues with one of the similiar proposals you provided. (Making the thing opt-in solves those issues by the way)
(To be clear, the type definition should be completely cloned)
const A = "something";
type A<T> = { idk: T };
const B = A as type;
// Same as
const B = A;
type B<T> = A<T>; // The type parameters are not automatically forwarded
This is a known pain point with class constructors, but not really the kind of thing people have been clamoring for with non-constructor values. The proposal you're talking about doesn't really work for constructors, or at least not consistently. There is no "attached type" for mixin(MyClass), it's just a value. There's no type "named" mixin(MyClass), and the "attached type" is... the instance type? You want const Result = mixin(MyClass) as type to do something fundamentally different from const b = a as type. How do you determine what the "attached type" is for an arbitrary value? What should const b = (1 + 1) as type do? All of this is certainly interesting, but I don't know why you want to water down your request by expanding it well past the use case you care about, to a region where people can talk for pages and pages about what the right thing to do is.
If I were you I would edit this to fix the request so it's not asking for a runtime feature (so class b = a is a no-go, even if we decided what it would mean) and to confine it to just class constructors... and then we can both hide all these comments that merely distract from what I think the main ask is here. It's your choice how to proceed, and I'm not a TS team member, so take or leave this advice as you see fit.
I updated the main suggestion to change the syntax, provide edge cases and state clearly that the second part of the suggestion is completely optional. I will change the suggestion again as needed as soon as I, eventually, get feedback from TS team members (Potentially removing the optional part altogether)