Class-factory mixins are a powerful concept for organizing re-usable class code. However, with the introduction of #hashtag private fields, mixins suffer the problem that private fields are not reliable across mixin applications (because technically each class returned by a mixin is a different class, but that’s not the intent developers typically have with private fields).
Another issue is that class-factory mixins are very difficult to use in TypeScript, requiring a lot of boilerplate, and riddled with issues causing TypeScript type features to be unavailable for that use case. For example private and protected members are not currently supported with class-factory mixins, among others.
The following issue details both problems:
What if we had a way to define a partial class, which can be used to define a concrete class later? Mixins could look like the following:
partial class Foo {
#foo = 123
meth() {
console.log(this.#foo)
}
}
It could then be used as a regular class to extend from, or as a mixin. Without types in plain JS, like this:
class MyClass extends Foo {}
new MyClass().meth() // logs 123
class OtherClass extends MyClass(AnotherClass) {}
new OtherClass().meth() // logs 123
A partial class cannot have an extends clause. A base class can only be provided to the partial class by the user of the partial class, and the extends becomes implicit with the given user base class.
For TypeScript, the type of base classes could be constrained with added type syntax, similar to a return type:
partial class Foo: HTMLElement {
#foo = 123
meth() {
console.log(this.#foo)
}
}
In this example, the Foo class is restricted to accepting only base classes that extend from HTMLElement.
class MyEl extends Foo(HTMLDivElement) {...}
class OtherEl extends Foo(Object) {...} // type error in TypeScript
The presence of the type annotation, in TypeScript, also means that APIs inside of the partial class can be type checked based on the base class constraint:
partial class Foo: HTMLElement {
#foo = 123
meth() {
console.log(this.#foo)
this.querySelector('.foo') // ok, this member is available due to the base class constraint.
}
}
If the colon is awkward there, TypeScript could also introduce a type-only keyword, f.e.
partial class Foo accepts HTMLElement {
...
}
or similar. Actually, I think I like accepts better than a colon!
This ES feature would
- allow for
#privatesyntax to work across all instances of the partial class, regardless of which base class is provided (similar to WeakMaps semantics that are defined outside the lexical scope of a mixin) - potentially make type definitions for mixins in TypeScript easy, or if technically the same complexity would encourage TypeScript to officially support mixin types due to alignment of TypeScript with EcmaScript, finally allowing TS developers to enjoy mixins without all the fuss but with all the practicality
It's also possible that the accepts HTMLElement idea for TypeScript, could be for JavaScript, and enforce an instanceof-like check to verify a given class is or extends from the required base class. Runtime type checking hasn't been a JS feature though.