Open
Description
The document gives some examples on how to use mixin here: https://www.typescriptlang.org/docs/handbook/mixins.html
However the 2 proposed solutions has issues:
- the first one prevent us from having private/protected members, and may generate quite a lot of type deduction error in the function.
- the second is not that great regarding to type safety.
I think we have a better solution to use mixin, that could be added to the documentation:
class Base {}
// declare the added properties outside of the mixin function
abstract class Mixin {}
class K extends mix(Base, Mixin) { }
Such generic function could be implemented like this:
const MixSrc = Symbol();
type Mix<
Base extends new(...args:any[])=>any,
Mixin extends abstract new(...args:any[])=>any,
> = Omit<Base & Mixin, "new"> & (new(...args:ConstructorParameters<Base>) => (InstanceType<Base> & InstanceType<Mixin>))
function mix<
Base extends new(...args:any[]) => any,
Mixin extends abstract new(...args:any[]) => any
>(base: Base, mixin: Mixin): Mix<Base, Mixin> {
class _ extends base {}
const static_props = Object.getOwnPropertyDescriptors(mixin);
delete static_props.prototype;
delete static_props.name;
Object.defineProperties( _, static_props );
((_ as any)[MixSrc] ??= []).push(mixin);
const hasInstance = mixin[Symbol.hasInstance];
Object.defineProperty(mixin, Symbol.hasInstance, {
value: function (instance: any) {
if( instance.constructor[MixSrc].includes(this) )
return true;
return hasInstance.call(this, instance);
},
writable: false,
});
const instance_props = Object.getOwnPropertyDescriptors(mixin.prototype);
// @ts-ignore
delete instance_props.constructor;
Object.defineProperties( _.prototype, instance_props );
return _ as any; // well...
}
There are 2 limitations of this method:
- This may not work if
Mixin
inherit from another class ( though you could just callmix
for each of theMixin
bases ). - This will not work if
Mixin
has private#
properties.
If we have private properties, we can do something like:
abstract class Mixin {
// declare the public/protected interface here
}
type ExcludeProtected<T> = {[K in keyof T]: T[K]}
function addMixin<...>(base): Mix<Base, Mixin> {
return class _ extends base implements ExcludeProtected<Mixin> {
// implements Mixin methods here...
// can't guarantee the protected interface (due to TS limitations).
} as any;
}
But then Mixin
must not inherit from another class.
Metadata
Metadata
Assignees
Labels
No labels