In my code, there is a function named Mixin
which requires a single argument in the form of a "class factory mixin".
For example, let's consider a scenario where I have a class factory mixin function like this:
type Constructor<T = any, A extends any[] = any[]> = new (...a: A) => T
const CoolMixin = <T extends Constructor>(Base: T) => {
return class CoolMixin extends Base {
coolProp = 42
}
}
const CoolFoo = CoolMixin(class Foo {
foo = 'asdf'
})
const c = new CoolFoo()
// it works:
c.foo
c.coolProp
By observing the above function, it can be noted that it accepts a base class and returns a new class, functioning effectively.
To enhance this functionality, I have developed a Mixin
utility that provides additional features such as hasInstance
support, caching against duplicate applications of base classes, and other functionalities.
In plain JavaScript, I utilize this utility as follows:
// Mixin returns an application of the Mixin function (a class) with
// a default base class applied (Object by default):
const CoolMixin = Mixin((Base) => {
return class CoolMixin extends Base {
coolProp = 42
}
})
// Here, CoolMixin is `class CoolMixin extends Object {...}`,
// thus allowing its usage similar to a regular class:
let CoolFoo = class Foo extends CoolMixin {
foo = 'asdf'
}
// Mixin returns that class with a static `.mixin` property containing
// the original mixin function, enabling its utilization as a mixin:
CoolFoo = CoolMixin.mixin(class Foo {
foo = 'asdf'
})
// Both versions work identically:
const c = new CoolFoo()
c.foo
c.coolProp
Hence, the advantage of my utility lies in its flexibility for convenient use. Here are two more examples:
// Suppose One and Two are mixins created using my Mixin utility.
// Use regular extension:
class Foo extends One {...}
class Bar extends Two {...}
// Or combine them together:
class Baz extends One.mixin(Two) {...}
Therefore, I aim to establish typing for this Mixin
utility in TypeScript.
My initial attempt showcases how I am trying to achieve this concept:
type Constructor<T = any, A extends any[] = any[]> = new (...a: A) => T
type MixinFunction = <TSub, TSuper>(base: Constructor<TSuper>) =>
Constructor<TSub & TSuper>
declare function Mixin<TSub, TSuper, T extends MixinFunction>(mixinFn: T):
Constructor<TSub & TSuper> & {mixin: T}
// Using it as follows:
const CoolMixinFunction = <T extends Constructor>(Base: T) => {
return class CoolMixin extends Base {
coolProp = 42
}
}
const CoolMixin = Mixin(CoolMixinFunction)
const CoolFoo = CoolMixin.mixin(class Foo {
foo = 'asdf'
}
const c = new CoolFoo()
c.foo
c.coolProp
const CoolBar = class Bar extends CoolMixin {
bar = 'asdf'
})
const b = new CoolBar()
b.bar
b.coolProp
Through this process, I intend to type the Mixin
tool to accept a mixin function, ensuring that the returned class from the Mixin
call is generated according to the principles laid out by the mixin function. Additionally, the generated class should feature a .mixin
property identical to the entered mixin function.
Despite realizing that there may be flaws in my approach, I seek guidance on implementing type inference effectively.
Given the latest advancements like the "Higher order function type inference" feature, I believe incorporating such tools could prove beneficial in this context.
How can I refine the typing for this Mixin
utility? Is it feasible without leveraging the higher-order feature mentioned earlier? If not, how would one integrate it using said advancements?