我正在用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也很陌生,因此任何指导都将不胜感激。谢谢!
答案 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);
}
}