در قسمت قبل با مشکل همیشگی کلیدواژه this برخورد کردیم و برای حل آن از روش شناخته شده bind استفاده کردیم اما به شما گفتم که ممکن است راه دیگری نیز وجود داشته باشد، مثلا استفاده از decorator ها! منظور من این است که ما یک decorator بسازیم که کلیدواژه this را به صورت خودکار bind کند و ما مجبور نباشیم این کار را به صورت دستی برای event-listener ها انجام دهیم. توجه داشته باشید که روش جلسه قبل هیچ مشکلی ندارد و هدف من از نوشتن decorator جدید، تمرین کردن مباحث قبلی است.
در قدم اول bind را از submitHandler پاک کنید:
private configure() { this.element.addEventListener('submit', this.submitHandler); }
حالا خارج از کلاس و در اول فایل شروع به نوشتن autobind decorator خود می کنیم. قبلا هم گفته بودیم که decorator ها فقط یک تابع هستند. decorator ما یک method decorator است بنابراین سه پارامتر می گیرد. چرا method decorator؟ به دلیل اینکه می خواهم آن را به به submitHandler متصل کنم تا اجرا شود و خودش مشکل this را حل کند:
// autobind decorator function autobind(target: any, methodName: string, descriptor: PropertyDescriptor) { } // ProjectInput Class class ProjectInput { // بقیه کدها //
حالا قبل از نوشتن کدهایش، آن را به submitHandler اضافه می کنم:
@autobind private submitHandler(event: Event) { event.preventDefault(); console.log(this.titleInputElement.value); }
حالا به decorator برگردید. در این decorator ابتدا باید به متد اصلی دسترسی پیدا کنیم بنابراین:
function autobind(target: any, methodName: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; }
در جلسات قبل توضیح دادیم که متدها در نهایت فقط یک خصوصیت (property) هستند و descriptor.value مقدار این خصوصیت (در اینجا، متد submitHandler) را به ما می دهد. در مرحله بعد باید آن را ویرایش کنیم بنابراین:
function autobind(target: any, methodName: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; const adjDescriptor: PropertyDescriptor = { configurable: true, get() { const boundFn = originalMethod.bind(this); return boundFn; } }; return adjDescriptor; }
adjDescriptor قرار است همان مقدار ویرایش شده ما را بگیرد. configurable را روی true گذاشته ایم تا همیشه بتوانیم آن را تغییر دهیم. در مرحله بعد یک getter می خواستیم که هنگام اجرای متد submitHandler اجرا خواهد شد. با استفاده از این getter می توانیم، هنگامی که کاربری بخواهد به خصوصیت مورد نظر (که در واقع یک متد است) دسترسی پیدا کند، کار های اضافه انجام بدهیم. یعنی این متد به محض صدا زده شدن توسط برنامه اجرا نشود بلکه ما تغییرات کوچکی را در آن اعمال کنیم و سپس بگذاریم اجرا شود.
ما درون این getter متد اصلی را گرفته (originalMethod) و سپس با استفاده از bind کلیدواژه this را به آن متصل می کنیم. سپس متد ویرایش شده را return کرده و خود adjDescriptor را نیز return می کنیم. با انجام این کار خطایی بر روی submitHandler دریافت می کنیم که می گوید گزینه experimental decorators را در فایل tsconfig.json فعال کنید بنابراین این کار را انجام می دهیم:
"experimentalDecorators": true
ما در مورد گزینه های فایل tsconfig.json توضیح مفصلی داده ایم بنابراین از تکرار آن ها پرهیز می کنم. با انجام این کار چند خطای دیگر می گیریم. این خطاها به ما می گویند که decorator ما پارامترهایی مانند target و methodName را گرفته اما هیچ وقت از آن ها استفاده نکرده است. یک راه حل برای این مشکل رفتن به فایل tsconfig.json و غیرفعال کردن قوانین strict (قسمت noUnusedParameters) است. یعنی ابتدا Strict را کامنت کنیم و سپس noUnusedParameters را از حالت کامنت خارج کرده و روی false قرار دهیم. این روش خیلی از مزایای تایپ اسکریپت (نوشتن کد تمیز و اصولی) را از ما می گیرد بنابراین پیشنهاد نمی شود. راه حل دیگر استفاده از _ است که در جلسات قبل مشاهده کرده بودید:
function autobind(_: any, _2: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; const adjDescriptor: PropertyDescriptor = { configurable: true, get() { const boundFn = originalMethod.bind(this); return boundFn; } }; return adjDescriptor; }
این نام گذاری خاص به تایپ اسکریپت می گوید که ما نیازی به این پارامترها نداریم (این پارامترها باید حتما دریافت شوند و نمی توانید آن ها را خالی بگذاریم به همین دلیل از این نوع خاص نام گذاری استفاده کرده ایم). حالا مشکل ما حل شده است و می توانیم آن را در مرورگر تست کنیم. قبل از تست آن در مرورگر مطمئن شوید که فایل ها را دوباره compile کرده باشید. حالا من مرورگر را باز می کنم و در قسمت title عبارت Hello را می نویسم. کنسول مرورگر را نیز باز می کنم و روی دکمه Add Project کلیک می کنم. نتیجه باید بدون مشاهده خطا در قسمت کنسول نمایش داده شود:
حرف نهایی ما این است: آیا استفاده از bind به صورت دستی آسان تر نیست؟ همانطور که گفتم استفاده از bind به صورت دستی هیچ مشکلی ندارد و هدف ما در این جلسه تمرین decorator ها بود اما جدا از این بحث باید به یک مطلب توجه کنیم. نوشتن bind به صورت دستی در پروژه های بسیار بزرگ که تیم های مختلفی روی آن کار می کنند، کمی خسته کننده می شود. شاید در یک پروژه صد ها event-listener داشته باشیم و صدا زدن bind برای هر کدام و پیدا کردن آن ها برای ویرایش سخت بشود اما decorator ها در ابتدای نام تابع اضافه می شوند بنابراین می توانیم کدهای خود را به صورت عادی بنویسیم و هر جا نیاز به bind بود از decorator ها استفاده کنیم. باز هم باید بگویم که در نهایت تمام این موارد به سلیقه خودتان برمی گردد و واقعا راه غلطی وجود ندارد اما برای جلو آمدن به همراه پروژه از همان decorator ها استفاده کنید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.