Расширение набора текста с помощью функции - PullRequest
0 голосов
/ 13 июня 2019

Я пытаюсь создать js SDK с некоторым плагином.

Я работаю над его набором.

Мой код выглядит примерно так:

import Sdk from "./sdk";
import ProductsPlugin from "./sdk/plugins/products";
import UsersPlugin from "./sdk/plugins/users";

const sdk = new Sdk({
  apiBaseUrl: "http://www.foo.bar"
});

sdk.usePlugin(ProductsPlugin)
sdk.usePlugin(UsersPlugin)

В данный момент я хотел бы расширить sdk методами, добавляемыми плагинами, такими как:

sdk.ProductsPlugin.addProcut(....)

Идея состоит в том, чтобы иметь небольшой SDK, который можно дополнить плагинами, которые нужны клиенту.

Как можно расширить набор текста, чтобы при наличии sdk.ProductsPlugin. я получил автозаполнение, предлагающее добавленные методы

Код пока выглядит примерно так:

class Sdk {
  usePlugin(PluginClass) {
    new PluginClass(this);
  }
}


class Plugin {
    sdk: Sdk;

    constructor(sdk: Sdk) {
        this.sdk = sdk;
        const pluginName = this.constructor.name.replace("Plugin", "");
        sdk[pluginName] = this;
    }
}

class FirstPlugin extends Plugin {

    boo(){
        alert(1)
    }
}


const instance = new Sdk()


instance.usePlugin(FirstPlugin)


instance.First.boo(); 

Проблема в том, что у меня нет автозаполнения для .First

1 Ответ

1 голос
/ 13 июня 2019

Я думаю, что для того, чтобы делать то, что вы хотите, вам, возможно, придется ослабить некоторые из ваших неявных требований.

Во-первых, у компилятора нет строгого способа понять, что 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();

Ссылка на код

Надеюсь, это даст вам некоторое направление.Удачи!

...