mixin-decorators
v1.0.1
Published
Mixin classes via decorators
Maintainers
Readme
Mixin classes via decorators
TypeScript 5.0 introduces us to ECMAScript decorators, which are quite different from the experimental decorators of the past. With a little bit of wiring, and some very specific constraints, we can build a new kind of mix-in class.
MultiMixinBuilder and its helper types (SubclassDecorator, StaticAndInstance most notably) provide everything you need to build out your mix-in. The main benefit of MultiMixinBuilder is it returns a Class with an aggregate type, combining all the static and instance fields you defined. Built-in TypeScript 5 decorators don't provide the aggregate type.
Example
import MultiMixinBuilder, {
type StaticAndInstance,
type SubclassDecorator,
} from "mixin-decorators";
class MixinBase {
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
constructor(...args: any[])
{
// do nothing
}
}
// #region XVector
declare const XVectorKey: unique symbol;
interface XVector extends StaticAndInstance<typeof XVectorKey> {
staticFields: {
xCoord: number;
}
instanceFields: {
get xLength(): number;
set xLength(value: number);
}
symbolKey: typeof XVectorKey;
}
const Mixin_XVector: SubclassDecorator<XVector, typeof MixinBase, false> = function(
this: void,
_class: typeof MixinBase,
context: ClassDecoratorContext<typeof MixinBase>,
)
{
if (context.kind !== "class") {
throw new Error("what's happening?")
}
return class extends _class {
static xCoord = 12;
xLength = 0;
constructor(...args: unknown[]) {
super(...args);
}
}
}
// #endregion XVector
// #region YVector
declare const YVectorKey: unique symbol;
interface YVector extends StaticAndInstance<typeof YVectorKey> {
staticFields: {
yCoord: number;
}
instanceFields: {
yLength: number;
}
symbolKey: typeof YVectorKey;
}
const Mixin_YVector: SubclassDecorator<YVector, typeof MixinBase, [number]> = function(
yCoordStatic: number
)
{
return function(
this: void,
_class: typeof MixinBase,
)
{
return class extends _class {
static yCoord = yCoordStatic;
yLength = 4;
}
}
}
// #endregion YVector
/*
const XYVector =
@Mixin_XVector
@Mixin_YVector(7)
class extends MixinBase {
};
*/
const XYVector = MultiMixinBuilder<[
XVector, YVector
], typeof MixinBase>
(
[
Mixin_XVector, Mixin_YVector(7)
], MixinBase
);
const xy = new XYVector;
it("xy", () => {
expect(xy.xLength).toBe(0);
expect(xy.yLength).toBe(4);
});
it("XYVector", () => {
expect(XYVector.xCoord).toBe(12);
expect(XYVector.yCoord).toBe(7);
});Without MultiMixinBuilder. the xLength and yLength properties of xy would be unknown to TypeScript. Likewise, TypeScript wouldn't know about XYVector.xCoord or XYVector.yCoord.
Installation
npm install --save-dev mixin-decorators
Under the hood
Type definitions
- A class decorator type which is aware of the new context argument. TypeScript 5.0's built-in
ClassDecoratortype won't work..ClassDecoratorFunctionfills the bill. - Classes have static fields, which means a special type to define the static and instance fields of a subclass.
StaticAndInstancedefines this. - Without depending on
StaticAndInstance, we need a type to define how a mix-in class joins the base class and its subclass's static and instance fields.MixinClassis a little convoluted, but works well. - Combining a base class with
StaticAndInstanceandClassDecoratorFunctionoffers aSubclassDecoratortype. An array ofStaticAndInstancetypes gives rise to aSubclassDecoratorSequencetype in the same file. - MultiMixinClass defines a
MixinClasstype from an array ofStaticAndInstanceobjects.
How do I use these types?
Classes
MultiMixinBuildercombines all of the above:- It takes a type parameter,
Interfaces, which is an ordered array ofStaticAndInstancetypes. - It takes a parameter,
decorators, which is aSubclassDecoratorSequencemapping theInterfacestoSubclassDecoratorinstances. - It also takes a parameter,
baseClass, which must be an subclass ofMixinBase. - It returns a
MultiMixinClassfrom the base class and invoking all theSubclassDecoratorfunctions.
- It takes a type parameter,
Rules to follow
- The ordering of decorators in
MultiMixinBuilderdetermines the chain of derived-to-base classes.
