mixin-decorators
v1.0.1
Published
Mixin classes via decorators
Downloads
14
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
ClassDecorator
type won't work..ClassDecoratorFunction
fills the bill. - Classes have static fields, which means a special type to define the static and instance fields of a subclass.
StaticAndInstance
defines 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.MixinClass
is a little convoluted, but works well. - Combining a base class with
StaticAndInstance
andClassDecoratorFunction
offers aSubclassDecorator
type. An array ofStaticAndInstance
types gives rise to aSubclassDecoratorSequence
type in the same file. - MultiMixinClass defines a
MixinClass
type from an array ofStaticAndInstance
objects.
How do I use these types?
Classes
MultiMixinBuilder
combines all of the above:- It takes a type parameter,
Interfaces
, which is an ordered array ofStaticAndInstance
types. - It takes a parameter,
decorators
, which is aSubclassDecoratorSequence
mapping theInterfaces
toSubclassDecorator
instances. - It also takes a parameter,
baseClass
, which must be an subclass ofMixinBase
. - It returns a
MultiMixinClass
from the base class and invoking all theSubclassDecorator
functions.
- It takes a type parameter,
Rules to follow
- The ordering of decorators in
MultiMixinBuilder
determines the chain of derived-to-base classes.