【问题标题】:Can I access the target class instance in a Typescript method decorator?我可以在 Typescript 方法装饰器中访问目标类实例吗?
【发布时间】:2020-08-09 20:24:50
【问题描述】:

我正在 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 也很陌生,所以任何指导都将不胜感激。谢谢!

【问题讨论】:

  • 我是否正确理解目标是在WebsocketHandler 发生某些事情时调用每个ListOfStuff 实例的listLengthRequest
  • 是的。 WebsocketHandler 内部的逻辑会在两个方法注册同一个 requestName 时产生警告,因此这实际上仅适用于创建一次的类。但是还有其他WebsocketHandler 回调我打算使用应该在每个实例上触发的装饰器来公开。

标签: typescript decorator


【解决方案1】:

无法在 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

【讨论】:

    【解决方案2】:

    以下代码 sn-p 显示了我们如何从方法的装饰器中访问目标类实例:

    function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
      const originalValue = descriptor.value;
    
      descriptor.value = function(...args: any[]) {
        // "this" here will refer to the class instance
        console.log(this.constructor.name);
    
        return originalValue.apply(this, args);
      }
    };
    

    用法:

    class Foo {
    
      @log
      bar() {
        // do something
      }
    }
    
    

    【讨论】:

    • 非常有趣的解决方案!
    猜你喜欢
    • 2011-01-22
    • 2011-06-26
    • 2012-12-15
    • 2019-08-21
    • 2019-02-03
    相关资源
    最近更新 更多