آیا میتوانم به نمونه کلاس مقصد در یک دکوراتور متد تایپاسکریپت دسترسی داشته باشم؟
من در حال ایجاد یک سرور 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 را به عنوان زمینهٔ handler با این تابع کارخانه ارسال کنم:
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
این اولین باری است که با دکوراتورها کار میکنم و من هم به تازگی با تایپاسکریپت آشنا شدهام، بنابراین هر راهنمایی بسیار قدردانی خواهد شد. متشکرم!
پاسخ شماره ۱
قطعه کد زیر نشان میدهد چگونه میتوانیم از درون یک دکوراتور برای متدها به نمونه کلاس هدف دسترسی پیدا کنیم:
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
}
}
پاسخ شماره ۲
دسترسی به یک نمونه در یک دکوراتور متد در تایپاسکریپت ممکن نیست. اما تغییر پروتوتایپ و سازنده با استفاده از دکوراتورها ممکن است. بنابراین راهحل ممکن استفاده از دو دکوراتور است: اولی که متدها را «علامتگذاری» میکند و دومی که سازنده را تغییر میدهد و منطق ثبت را اضافه میکند.
در ادامه سعی میکنم این ایده را توضیح دهم
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);
}
}
ریپل بهروزرسانی شد
