Могу ли я получить доступ к экземпляру целевого класса в декораторе метода Typescript? - PullRequest
2 голосов
/ 26 апреля 2020

Я создаю сервер WebSocket в Typescript, в котором разные компоненты приложения должны иметь возможность регистрировать свои собственные обработчики запросов. Есть синглтон WebsocketHandler, обеспечивающий такое поведение.

Без декораторов класс может зарегистрировать свои обработчики запросов следующим образом:

class ListOfStuff {
  private list = [];

  constructor() {
    //Register listLengthRequest as a request handler
    WebsocketHandler.getInstance().registerRequestHandler('getListLength', () => this.listLengthRequest());
  }

  private listLengthRequest() : WebSocketResponse {
    return new WebSocketResponse(this.list.length);
  }
}

class WebsocketHandler {
  private constructor() {}

  private static instance = new WebsocketHandler();

  static getInstance() {
    return this.instance;
  }

  registerRequestHandler(requestName: string, handler: () => WebSocketResponse) {
    //Store this handler in a map for when a request is received later 
  }
}

class WebSocketResponse {
  constructor(content: any) {}
}

, который работает нормально. Однако я пытаюсь заменить регистрационные вызовы в конструкторе на методы-декораторы. В идеале ListOfStuff должен выглядеть следующим образом:

class ListOfStuff {
  private list = [];

  @websocketRequest("getListLength")
  private listLengthRequest() : WebSocketResponse {
    return new WebSocketResponse(this.list.length);
  }
}

Но после создания фабрики декораторов для @websocketRequest я не могу понять, как заставить listLengthRequest() выполняться в правильном контексте. Я пробовал эту заводскую функцию:

function websocketRequest(requestName: string) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    WebsocketHandler.getInstance().registerRequestHandler(requestName, descriptor.value);
  }
}

, которая делает this равным карте, в которой удерживается функция (внутри WebsocketHandler).

Затем я попытался передать target в качестве контекста обработчика с помощью этой фабричной функции:

function websocketRequest(requestName: string) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    WebsocketHandler.getInstance().registerRequestHandler(requestName, () => descriptor.value.call(target));
  }
}

Но затем я понял, что target относится только к прототипу ListOfStuff и не фактический экземпляр класса. Так что все еще бесполезно.

Можно ли как-нибудь получить экземпляр ListOfStuff (чтобы я мог получить доступ к this.list) на фабрике декораторов? Должен ли я структурировать свой декоратор каким-либо другим способом, чтобы он был привязан к экземпляру класса, а не к его прототипу? Вот Repl, демонстрирующий проблему https://repl.it/@Chap / WonderfulLuckyDatalogs

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

1 Ответ

1 голос
/ 27 апреля 2020

Невозможно создать экземпляр в декораторе метода Typescript. Но изменить прототип и конструктор можно с помощью декораторов. Таким образом, возможное решение состоит в том, чтобы использовать два декоратора: первый, который «помечает» методы, и второй, который меняет конструктор, добавляя регистрационные логи c.

Ниже я попытаюсь проиллюстрировать идею

const SubMethods = Symbol('SubMethods'); // just to be sure there won't be collisions

function WebsocketRequest(requestName: string) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    target[SubMethods] = target[SubMethods] || new Map();
    // Here we just add some information that class decorator will use
    target[SubMethods].set(propertyKey, requestName);
  };
}

function WebSocketListener<T extends { new(...args: any[]): {} }>(Base: T) {
  return class extends Base {
    constructor(...args: any[]) {
      super(...args);
      const subMethods = Base.prototype[SubMethods];
      if (subMethods) {
        subMethods.forEach((requestName: string, method: string) => {
          WebsocketHandler.getInstance()
            .registerRequestHandler(
              requestName,
              () => (this as any)[method]()
            );
        });
      }
    }
  };
}

Использование:

@WebsocketListener
class ListOfStuff {
  private list = [];

  @WebsocketRequest("getListLength")
  private listLengthRequest() : WebSocketResponse {
    return new WebSocketResponse(this.list.length);
  }
}

Обновлен репл

...