Я думаю, что для того, чтобы делать то, что вы хотите, вам, возможно, придется ослабить некоторые из ваших неявных требований.
Во-первых, у компилятора нет строгого способа понять, что sdk.usePlugin()
изменит тип из sdk
, чтобы иметь больше свойств.Самое близкое, что вы получите - это sdk.usePlugin()
return this
измененного типа и использование цепочки методов (как в fluent interface ) при настройке Sdk
.
Во-вторых, если вы хотите, чтобы компилятор статически знал, каким будет имя плагина в Sdk
, вам нужно предоставить его как строковый литерал типа , который является частью вашего Plugin
определение типа.Возможно, Plugin
должен даже быть универсальным классом, который зависит от этого имени (и абстрактный, если его нужно разделить на подклассы, чтобы быть полезным)
Вот как вы можете это сделать:
class Sdk {
usePlugin<P extends Plugin<any>>(PluginClass: new (sdk: Sdk) => P) {
new PluginClass(this);
return this as this & Record<ReturnType<P["getPluginName"]>, P>;
}
}
abstract class Plugin<Name extends string> {
sdk: Sdk & Record<Name, this>;
abstract getPluginName(): Name; // have to let the compiler know what name to use
constructor(sdk: Sdk) {
this.sdk = sdk as any; // hard to convince compiler this is okay
this.sdk[this.getPluginName()] = this as any; // hard to convince the compiler this is okay
}
}
class FirstPlugin extends Plugin<"First"> {
getPluginName() {
return "First" as "First"; // or as const in TS3.4+
}
boo() {
alert(1);
}
}
const instance = new Sdk();
// have to assign to new var
const instanceWithFirst = instance.usePlugin(FirstPlugin);
instanceWithFirst.First.boo();
Ссылка на код
Надеюсь, это даст вам некоторое направление.Удачи!