我可以在Typescript方法装饰器中访问目标类实例吗?

时间:2020-04-26 10:34:24

标签: typescript decorator

我正在用Typescript创建一个WebSocket服务器,其中不同的应用程序组件应该能够注册自己的请求处理程序。有一个单例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 个答案:

答案 0 :(得分:2)

无法在Typescript方法装饰器中访问实例。但是可以使用装饰器更改原型和构造器。因此,可能的解决方案是使用两个修饰符:第一个修饰符“标记”方法,第二个修饰符更改构造函数并添加注册逻辑。

下面我将尝试说明这个想法

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);
  }
}

Updated repl

相关问题