Last active
May 4, 2021 10:20
-
-
Save gund/b4ace5b04f016f64388537a5defd8cfc to your computer and use it in GitHub Desktop.
Tree shakable class methods implemented via visitor pattern with native Typescript support
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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