'Extend class with Plugin Architecture
Say I have a class Test with a static method .plugin which takes a function. That function can run arbitrary code and extend Tests API.
const MyTest = Test.plugin(fooPlugin)
const test = new Test()
const myTest = new MyTest()
test.foo // does not exist
myTest.foo // exists
I've made a TypeScript Playground that I hope is close to working
When I add myTest.foo to the end of the example, .foo is typed as any. I would expect that the <typeof plugin> would return the type of the plugin function that I pass, not the generic specification?
If I replace <typeof plugin> with <typeof TestPlugin> then it works as expected.
Is there anything I can do to make this work, without changing the way the Plugin Architecture currently works?
If I slightly change the code (Playground link), myTest.foo gets typed correctly, but there are two TypeScript errors.
Solution 1:[1]
Your modified approach is almost correct, just that T = Plugin is a default value of Plugin for the type parameter T, but T can be any other type not necessarily a subtype of Plugin. You want to say T extends Plugin which means T must be a subtype of Plugin.
Also you don't need the index signature in Test (at least not as far as the plugin architecture is concerned). This will make missing members an error as well (the index signature would hide those):
type ApiExtension = { [key: string]: any }
export type Plugin = (instance: Test) => ApiExtension;
type Constructor<T> = new (...args: any[]) => T;
class Test {
static plugins: Plugin[] = [];
static plugin<T extends Plugin>(plugin: T) {
const currentPlugins = this.plugins;
class NewTest extends this {
static plugins = currentPlugins.concat(plugin);
}
type Extension = ReturnType<T>
return NewTest as typeof NewTest & Constructor<Extension>;
}
constructor() {
// apply plugins
// https://stackoverflow.com/a/16345172
const classConstructor = this.constructor as typeof Test;
classConstructor.plugins.forEach(plugin => {
Object.assign(this, plugin(this))
});
}
}
// Question: how to make Typescript understand that MyTest instances have a .foo() method now?
type TestPluginExtension = {
foo(): 'bar'
}
const TestPlugin = (test: Test): TestPluginExtension => {
console.log('plugin evalutes')
return {
foo: () => 'bar'
}
}
const MyTest = Test.plugin(TestPlugin)
const myTest = new MyTest()
myTest.foo()
myTest.fooo() //err
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Titian Cernicova-Dragomir |
