Skip to content

Instantly share code, notes, and snippets.

@gund
Last active May 4, 2021 10:20
Show Gist options
  • Save gund/b4ace5b04f016f64388537a5defd8cfc to your computer and use it in GitHub Desktop.
Save gund/b4ace5b04f016f64388537a5defd8cfc to your computer and use it in GitHub Desktop.
Tree shakable class methods implemented via visitor pattern with native Typescript support
// First define standard JS constructor container
type Constructable<T> = new (...args: any[]) => T;
type ConstructableAbstract<T> = abstract new (...args: any[]) => T;
type InferConstructable<T> =
T extends Constructable<infer C> ? C :
T extends ConstructableAbstract<infer C> ? C : never;
// Now let's define visitor interfaces
interface Visitable<T> {
apply<OP extends VisitOperator<T>>(
op: OP,
...args: Parameters<OP>
): ReturnType<OP>;
}
type VisitOperator<T> = (this: T, ...args: any[]) => any;
// And for ease of use lets define a visitor mixin
// Capture optional base class in nested function so TS inferrence is not broken
function visitable<T>() {
return <B extends ConstructableAbstract<object>, TB = T & InferConstructable<B>>(
base: B = Object as any,
): ConstructableAbstract<Visitable<TB>> & B => {
abstract class VisitableMixin extends base implements Visitable<TB> {
apply<OP extends VisitOperator<TB>>(
this: TB,
op: OP,
...args: Parameters<OP>
): ReturnType<OP> {
return op.apply(this, args);
}
}
return VisitableMixin;
};
}
// And now we can add visitor to any class
interface Storable {
value?: string;
}
class BaseStore {
protected sharedKey?: string;
}
class Store extends visitable<Storable>()(BaseStore) {
protected value: Storable['value'];
}
function storeGet(this: Storable): string | undefined {
console.log(`Getting value`, this.value);
return this.value;
}
function storeSet(this: Storable, value: string): void {
console.log(`Setting value`, value);
this.value = value;
}
const myStore = new Store();
// Now we call methods like this
// by importing each method we keep it only if it's used
myStore.apply(storeSet, '123');
myStore.apply(storeGet);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment