جاوااسکریپت یک زبان برنامه نویسی شگفتانگیز است و به ما این امکان را میدهد تا بتوانیم تقریباً روی هر پلتفرمی برنامه خود را بسازیم. اما تایپ اسکریپت دارای ویژگیهایی است که برای پوشاندن برخی از شکافهای ذاتی جاوااسکریپت بسیار خوب عمل میکند، به این صورت که نه تنها ایمنی تایپ را به یک زبان پویا اضافه میکند بلکه دارای ویژگیهای جالبی است که هنوز در جاوااسکریپت وجود ندارد مانند decorator ها.
اگرچه ممکن است این تعریف برای زبانهای برنامه نویسی مختلف متفاوت باشد، اما دلیل وجود decoratorها تقریباً در همه موارد یکسان است. به طور خلاصه، decorator الگویی در برنامه نویسی است که با wrapping، رفتار برخی از کدها را تغییر میدهد.
در جاوااسکریپت، این ویژگی در حال حاضر در stage two قرار دارد زیرا هنوز در مرورگرها یا Node.js موجود نیست. اما میتوانیم آن را با استفاده از کامپایلرهایی مانند Babel شبیهسازی و تست کنیم.
حتی اگر این ویژگی در جاوااسکریپت قابل پیادهسازی باشد، ویژگی decorator تایپ اسکریپت از چند جنبه قابل توجه متفاوت است. از آنجایی که تایپ اسکریپت یک زبان با تایپ قوی به حساب میآید، میتوانیم به برخی اطلاعات اضافی مرتبط با انواع دادههای خود دسترسی داشته باشیم تا کارهای جالبی مانند تایید تایپ زمان اجرا و dependency injection انجام دهیم.
این کار را با ایجاد یک پروژه Node.js خالی شروع میکنیم.
$ mkdir typescript-decorators $ cd typescript decorators $ npm init -y
سپس تایپ اسکریپت را به عنوان یک dependency توسعه نصب میکنیم.
$ npm install -D typescript @types/node
پکیج @types/node
شامل تعاریف تایپ Node.js برای تایپ اسکریپت است. برای دسترسی به برخی از کتابخانههای استاندارد Node.js به این پکیج نیاز داریم.
همینطور برای کامپایل کردن کد تایپ اسکریپت، اسکریپت npm زیر را در فایل package.json
اضافه میکنیم:
{ // ... "scripts": { "build": "tsc" } }
تایپ اسکریپت این ویژگی را به شکل تجربی برچسبگذاری کرده است. با این وجود، برای استفاده از آن در ساخت برنامهها به اندازه کافی پایدار میباشد. در واقع، جامعه open source مدت زیادی است که از آن استفاده میکند.
برای فعال کردن این ویژگی، باید تنظیماتی را در فایل tsconfig.json
خود انجام دهیم.
{ "compilerOptions": { "target": "ES5", "experimentalDecorators": true } }
سپس در ادامه یک فایل تایپ اسکریپت ساده ایجاد میکنیم تا آن را تست کنیم.
console.log("Hello, world!"); $ npm run build $ node index.js Hello, world!
به جای این که این دستور را بارها و بارها تکرار کنیم میتوانیم فرآیند کامپایل و اجرا را با استفاده از پکیجی به نام ts-node
سادهتر کنیم. این یک پکیج جامع است که به ما این امکان را میدهد تا کد تایپ اسکریپت خود را مستقیماً و بدون کامپایل کردن آن اجرا کنیم.
این پکیج را به عنوان یک dependency توسعه به شکل زیر نصب میکنیم:
$ npm install -D ts-node
سپس یک اسکریپت start
به فایل package.json
اضافه میکنیم:
{ "scripts": { "build": "tsc", "start": "ts-node index.ts" } }
برای اجرای کد خود کافی است دستور npm start
را اجرا کنیم:
$ npm start Hello, world!
در تایپ اسکریپت، decorator ها توابعی هستند که میتوانند به کلاسها و اعضای آنها مانند متدها و ویژگیها متصل شوند. در ادامه چند نمونه را باهم بررسی خواهیم کرد.
هنگامی که تابعی را به عنوان decorator به یک کلاس متصل میکنیم، سازنده کلاس را به عنوان اولین پارامتر دریافت خواهیم کرد.
const classDecorator = (target: Function) => { // do something with your class } @classDecorator class Rocket {}
اگر بخواهیم خصوصیات درون کلاس را نادیده بگیریم، میتوانیم یک کلاس جدید return کنیم و سازنده آن را extend کرده سپس ویژگیها را طبق خواسته خود تنظیم کنیم.
const addFuelToRocket = (target: Function) => { return class extends target { fuel = 100 } } @addFuelToRocket class Rocket {}
اکنون کلاس Rocket
ما دارای ویژگی fuel
با مقدار پیشفرض ۱۰۰
خواهد بود.
const rocket = new Rocket() console.log((rocket).fuel) // 100
یکی دیگر از قسمتهای خوب برای استفاده از decorator، متد class میباشد. در اینجا، ما سه پارامتر را در تابع خود دریافت میکنیم که عبارتند از: target
، propertyKey
و descriptor
.
const myDecorator = (target: Object, propertyKey: string, descriptor: PropertyDescriptor) => { // do something with your method } class Rocket { @myDecorator launch() { console.log("Launching rocket in 3... 2... 1... 🚀") } }
پارامتر اول شامل کلاسی است که این متد در آن قرار دارد که در مثال ما کلاس Rocket میباشد. پارامتر دوم شامل نام متد در قالب رشته بوده و آخرین پارامتر توصیفگر ویژگی است، یعنی مجموعهای از اطلاعات که یک رفتار ویژگی را تعریف میکند. این را میتوانیم برای مشاهده، اصلاح یا جایگزینی تعریف متد مورد استفاده قرار دهیم.
اگر بخواهیم functionality متد خود را گسترش دهیم که بعداً به آن خواهیم پرداخت، method decorator میتواند بسیار مفید باشد.
در این مورد نیز مانند method decorator، پارامتر target
و propertyKey
را دریافت میکنیم. تنها تفاوت این است که اینجا توصیفگر ویژگی وجود ندارد.
const propertyDecorator = (target: Object, propertyKey: string) => { // do something with your property }
پس از این که با decoratorها و نحوه استفاده درست از آنها آشنا شدیم، اکنون میخواهیم تا در مورد مشکلاتی که decoratorها میتوانند در حل کردن آنها به ما کمک کنند صحبت کنیم.
فرض کنید میخواهیم مدت زمان لازم برای اجرای یک تابع را به عنوان متدی برای سنجش عملکرد برنامه تخمین بزنیم. برای انجام این کار میتوانیم یک decorator برای محاسبه زمان اجرای یک متد ایجاد کنیم و آن را روی کنسول چاپ کنیم.
class Rocket { @measure launch() { console.log("Launching in 3... 2... 1... 🚀"); } }
کلاس Rocket
یک متد launch
در داخل خود دارد. برای اندازهگیری زمان اجرای متد launch
میتوانیم دکوراتور measure
را مورد استفاده قرار دهیم.
const measure = ( target: Object, propertyKey: string, descriptor: PropertyDescriptor ) => { const originalMethod = descriptor.value; descriptor.value = function (...args) { const start = performance.now(); const result = originalMethod.apply(this, args); const finish = performance.now(); console.log(`Execution time: ${finish - start} milliseconds`); return result; }; return descriptor; };
همانطور که می بینیم، دکوراتور measure
متد اصلی را با متد جدیدی جایگزین میکند که این امکان را میدهد تا زمان اجرای متد اصلی را محاسبه کرده و نتیجه را در کنسول نمایش دهد.
برای محاسبه زمان اجرا، از Performance Hooks API از کتابخانه استاندارد Node.js استفاده میکنیم.
نمونه جدیدی از Rocket
را ایجاد کرده و متد launch
را فراخوانی میکنیم.
const rocket = new Rocket(); rocket.launch();
در نهایت نتیجه زیر را دریافت خواهیم کرد:
Launching in 3... 2... 1... 🚀 Execution time: 1.0407989993691444 milliseconds
برای پیکربندی decoratorهای خود به گونهای که در یک سناریوی خاص به گونهای متفاوت عمل کنند، میتوانیم از مفهومی به نام decorator factory استفاده کنیم.
decorator factory تابعی است که decorator را برمیگرداند. این کار ما را قادر میسازد تا رفتار decoratorهای خود را شخصیسازی کنیم. به عنوان مثال:
const changeValue = (value) => (target: Object, propertyKey: string) => { Object.defineProperty(target, propertyKey, { value }); };
تابع changeValue
یک decorator را برمیگرداند که مقدار ویژگی را بر اساس مقدار ارسال شده تغییر میدهد.
class Rocket { @changeValue(100) fuel = 50 } const rocket = new Rocket() console.log(rocket.fuel) // 100
حال اگر decorator factory خود را به ویژگی fuel
متصل کنیم، مقدار آن برابر با ۱۰۰
خواهد شد.
اکنون قصد داریم تا آنچه را که تا این قسمت مقاله یاد گرفتهایم برای حل یک مشکل در دنیای واقعی به کار بگیریم.
class Rocket { fuel = 50; launchToMars() { console.log("Launching to Mars in 3... 2... 1... 🚀"); } }
فرض کنید یک کلاس Rocket
داریم که متد launchToMars
را شامل میشود. برای پرتاب موشک به مریخ، سطح سوخت باید بالای ۱۰۰ باشد.
حال decorator را برای آن ایجاد میکنیم:
const minimumFuel = (fuel: number) => ( target: Object, propertyKey: string, descriptor: PropertyDescriptor ) => { const originalMethod = descriptor.value; descriptor.value = function (...args) { if (this.fuel > fuel) { originalMethod.apply(this, args); } else { console.log("Not enough fuel!"); } }; return descriptor; };
MinimumFuel
یک decorator factory است. پارامتر fuel
، که بیانگر میزان سوخت لازم برای پرتاب موشک میباشد را دریافت میکند.
برای بررسی وضعیت سوخت، متد اصلی را مانند مورد استفاده قبلی داخل متد جدید قرار میدهیم.
اکنون میتوانیم decorator خود را به متد launchToMars
متصل کرده و حداقل سطح سوخت را تنظیم کنیم.
class Rocket { fuel = 50; @minimumFuel(100) launchToMars() { console.log("Launching to Mars in 3... 2... 1... 🚀"); } }
حال اگر از متد launchToMars
استفاده کنیم، موشک را به مریخ پرتاب نمیکند زیرا سطح سوخت فعلی ۵۰ میباشد.
const rocket = new Rocket() rocket.launchToMars() Not enough fuel!
نکته جالب در مورد این decorator این است که میتوانیم همان منطق را در روشی متفاوت بدون بازنویسی کل عبارت if-else اعمال کنیم.
فرض کنید میخواهیم روش جدیدی برای پرتاب موشک این بار به ماه ایجاد کنیم. برای انجام این کار، سطح سوخت باید بالای ۲۵ باشد. همان کد را تکرار کرده و پارامتر را تغییر میدهیم.
class Rocket { fuel = 50; @minimumFuel(100) launchToMars() { console.log("Launching to Mars in 3... 2... 1... 🚀"); } @minimumFuel(25) launchToMoon() { console.log("Launching to Moon in 3... 2... 1... 🚀") } }
اکنون میتوانیم این موشک را به ماه پرتاب کنیم.
const rocket = new Rocket() rocket.launchToMoon() Launching to Moon in 3... 2... 1... 🚀
این نوع decorator میتواند برای اهداف احراز هویت و مجوز بسیار مفید باشد، مانند بررسی اینکه آیا کاربر مجاز است به برخی از دادههای خصوصی دسترسی داشته باشد یا خیر.
باید به این نکته توجه داشته باشیم که در برخی از حالات نیازی به ساختن decorator نیست، زیرا بسیاری از کتابخانهها و فریمورکهای تایپ اسکریپت در حال حاضر تمام decoratorهای مورد نیاز ما را فراهم میکنند. اما درک این موضوع که در بکگراند آنها چه میگذرد میتواند بسیار جذاب باشد و یادگیری آن کمک میکند تا در صورت نیاز این امکان وجود داشته باشد که بتوانیم به راحتی decorator مورد نیاز خود را بسازیم.
۵۰ درصد تخفیف ویژه زمستان فرانت کست تا ۱۴ دی
کد تخفیف: wnt
دیدگاهها: