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

Cha*_*nan 3 decorator typescript

我正在用 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) {}
}
Run Code Online (Sandbox Code Playgroud)

哪个工作正常。但是,我试图用方法装饰器替换构造函数中的注册调用。理想情况下,ListOfStuff 将如下所示:

class ListOfStuff {
  private list = [];

  @websocketRequest("getListLength")
  private listLengthRequest() : WebSocketResponse {
    return new WebSocketResponse(this.list.length);
  }
}
Run Code Online (Sandbox Code Playgroud)

但是,在为 创build了一个装饰器工厂之后@websocketRequest,我无法弄清楚如何listLengthRequest()在正确的上下文中执行。我试过这个工厂功能:

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

这使得this等于函数被保存在(内部WebsocketHandler)的地图。

然后我尝试target使用这个工厂函数作为处理程序的上下文传入:

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

但后来我意识到这target只是指代的原型,ListOfStuff而不是实际的类实例。所以仍然没有帮助。

有什么方法可以在装饰器工厂中ListOfStuff获取(以便我可以访问this.list)的实例?我应该以其他方式构造我的装饰器,以便它与类的实例而不是它的原型相关联吗?这是一个演示该问题的 Repl https://repl.it/@Chap/WonderfulLuckyDatalogs

这是我第一次搞乱装饰器,而且我对 Typescript 也很陌生,所以任何指导都将不胜感激。谢谢!

小智 22

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

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);
  }
};
Run Code Online (Sandbox Code Playgroud)

用法:

class Foo {

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

Run Code Online (Sandbox Code Playgroud)


Shl*_*ang 6

无法访问 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]()
            );
        });
      }
    }
  };
}
Run Code Online (Sandbox Code Playgroud)

用法:

@WebsocketListener
class ListOfStuff {
  private list = [];

  @WebsocketRequest("getListLength")
  private listLengthRequest() : WebSocketResponse {
    return new WebSocketResponse(this.list.length);
  }
}
Run Code Online (Sandbox Code Playgroud)

更新回复