Skip to main content

How to write Mixin Functions

When writing the body of a mixin function, the standard structure is to return a new class that dynamically extends the base class.

When defining a mixin function, the basic form of the definition is as follows:

import { Component, Store } from '@geajs/core'

// Define a constructor type
type Constructor<T = Component> = new (...args: any[]) => T;
type MixinConstructor<TBase, Mixin> = new (...args: ConstructorParameters<TBase>) => Mixin;

// Define the type of the functionality added by the mixin
export interface XxxMixin {
xxxProperty: string
doSomething(): void
}

// Mixin Function Definition
export function withXxx<TBase extends Constructor<Component>>(Base: TBase) {
// Returns a new anonymous class that inherits from the base class.
const Derived = class extends Base implements XxxMixin {
// Properties to Add
xxxProperty = 'xxxProperty is a property.'

// Constructor
constructor(...args: any[]) {
super(...args)
// If necessary, run the initialization logic here.
}

// Methods to Add
doSomething() {
console.log('Hello World! ', this.xxxProperty)
}

// Overriding Cleanup Processing
dispose() {
if (typeof super.dispose === 'function') super.dispose();
}
}
return Derived as unknown as TBase & MixinConstructor<TBase, XxxMixin>
}

Type Definition​

type Constructor<T = Component> = new (...args: any[]) => T;
type MixinConstructor<TBase, Mixin> = new (...args: ConstructorParameters<TBase>) => Mixin;

export interface XxxMixin {
xxxProperty: string
doSomething(): void
}
  • Constructor<T>: A TypeScript type that represents a class constructor that returns an instance of type T. This type ensures that the function you are about to create can accept any class.
  • MixinConstructor<TBase, Mixin>: This serves as the base type for the types that are combined when returning a mixin. We use the built-in ConstructorParameters to store type information.
  • XxxMixin: The type—that is, the blueprint—of the new properties and methods added after this mixin is combined.

Function Definitions and Generics (Arguments)​

export function withXxx<TBase extends Constructor<Component>>(Base: TBase) {
const Derived = class extends Base implements XxxMixin {
  • <TBase extends Constructor<Component>>: Stores the original class type of the passed-in Component, Store, etc., as TBase.
  • (Base: TBase): Takes the original class as an argument.
  • const Derived = class extends Base ...: Creates a new class that inherits from the class passed as an argument.
  • implements XxxMixin: Checks whether the new class correctly satisfies the type definition specified at the beginning.

Properties and Constructors​

xxxProperty = 'initialized'

constructor(...args: any[]) {
super(...args)
}
  • xxxProperty: New data provided by this mixin.
  • super(...args): Since we don't know what arguments the original Base class requires, we use ...args to accept all arguments as an array and pass them directly to the original class's constructor. Be sure not to forget this. If you do, the parent class's initialization will not run, which can cause bugs.

Methods and Cleanup​

doSomething() {
console.log('Hello World! ', this.xxxProperty)
}

dispose() {
super.dispose();
}
  • doSomething(): This is a new method being added.
  • dispose(): This is arguably the most important method in GeaJS's memory management. Be sure to call super.dispose() to ensure that objects are disposed of in a chained manner and to prevent memory leaks.

Type Merging​

}
return Derived as unknown as TBase & MixinConstructor<TBase, XxxMixin>
}
  • TypeScript is not particularly good at automatically inferring complex types, such as those created by dynamically combining classes within functions.
  • Therefore, by using as unknown as, we tell TypeScript that this function returns a new class that combines the original class and one mixin.

However, as it stands, this only allows for extensions to Component because TBase is restricted to Constructor<Component>; you cannot create extensions for plain classes or other classes, such as Store. On the next page, let's learn how to create more general-purpose mixins.