ابزار تایپ‌اسکریپت افزونه‌ای از زبان برنامه‌نویسی جاوا اسکریپت است که از ران‌تایم جاوا همراه با یک «ابزار بررسی اشکال تایپی در هنگام کامپایل» استفاده می کند. این ترکیب باعث می‌شود که توسعه‌دهندگان بتوانند به طور کامل از اکوسیستم جاوا اسکریپت و ویژگی‌های آن بهره‌مند شوند. یکی از این ویژگی‌ها، پشتیبانی از ابزارهای موسوم به «دکوراتور» هستند.

دکوراتورها راهی برای طراحی اعضای یک کلاس و یا خودِ کلاس به شمار می‌روند و در عین حال، بر کارآیی آن می‌افزایند. وقتی یک دکوراتور را به یک کلاس یا یکی از اعضای کلاس اضافه می‌کنید، در واقع، تابعی را فراخوانی می‌کنید که قرار است جزئیات چیزی را که قرار است دکوراتور بر روی آن عمل کند، دریافت نماید. سپس این دکوراتور قادر خواهد بود که کد را به صورت دینامیک تغییر داده، کارآیی اضافه‌ای برای آن ایجاد کند و حجم کد را کاهش دهد. در ابزار تایپ‌اسکریپت روشی تحت عنوان «فرابرنامه‌نویسی» وجود دارد که در واقع، تکنیکی برای ساخت کدی است که می‌تواند از سایر کدهای اپلیکیشن به عنوان داده استفاده می‌نماید.

در حال حاضر، اضافه‌کردن دکوراتورها به استاندارد ECMAScript هنوز به پایان نرسیده است و در حال حاضر، جاوا اسکریپت از این قابلیت برخوردار نیست. امّا ابزار تایپ‌اسکریپت از ویژگی‌های دکوراتور خاص خود و البته به صورت «آزمایشی» بهره می‌برد.

در این  آموزش، نحوه ساخت دکوراتور در تایپ‌اسکریپت برای کلاس‌ها و اعضای کلاس، و همچنین نحوه استفاده از آن نشان داده می‌شود. برای این منظور، کدهای نمونه مختلفی بررسی شده که می‌تواند در انها در فضای کاری ابزار تایپ‌اسکریپت خودتان و یا به صورت آنلاین در مرورگر استفاده کنید.

پیش‌نیازها

برای دنبال کردن مراحل این ‌آموزش، به موارد زیر احتیاج خواهید داشت.

یک فضای کاری که در آن بتوانیم برنامه‌های تایپ‌اسکریپت را همراه با مثال‌ها اجرا کنید. برای داشتن این تنظیمات باید در سیستم خود موارد زیر را داشته باشید.

  • نصب دو ابزار Node و npm (یا yarn) برای اجرای محیط توسعه به منظور اجرای بسته‌های مرتبط با ابزار تایپ‌اسکریپت. در این آموزش از js نسخه Node.js و npm نسخه 6.14.5 استفاده شده است.
  • به علاوه، به کامپایلر تایپ‌اسکریپت یا tsc نیاز خواهید داشت. برای این منظور، حتماً به وب‌سایت رسمی تایپ‌اسکریپت مراجعه کنید.

در صورتی که نمی‌خواهید محیط تایپ‌اسکریپت را در سیستم محلی خود ایجاد کنید، می‌تانید از محیط آزمایشی ابزار تایپ‌اسکریپت برای این منظور استفاده نمایید.

همچنین به دانش مناسب در مورد جاوا اسکریپت، و مخصوصاً قالب ES6+ از جمله ویژگی‌ destructuring، عملگرهای rest و ایمپورت/ اکسپورت احتیاج دارید.

در این آموزش به ویژگی‌های ویرایشگرهای تنی که از ابزار تایپ‌اسکریپت و نمایش خطاهای درون‌خطی پشتیبانی می‌کنند، پرداخته می‌‌شود. البته برای این منظور، استفاده از تایپ‌اسکریپت ضرور نیست، ولی در عین حال، مزایای این ابزار به کار گرفته می‌شوند. برنامه Visual Studio Code می‌تواند پشتیبانی کاملی از تایپ‌اسکریپت برایتان به همراه خواهد داشت. همچنین در محیط آزمایشی موسوم به TypeScript Playground از این مزایا استفاده کنید.

در تمام مثال‌های این آموزش از نسخه 4.2.2 از ابزار تایپ‌اسکریپت استفاده شده است.

فعال‌سازی دکوراتورها در ابزار تایپ‌اسکریپت

دکوراتورها در حال حاضر یک ویژگی ‌آزمایشی در تایپ‌اسکریپت به شمار می‌روند و به همین دلیل، ابتدا باید ‌آنها را فعال‌سازی نمود. در این بخش، نحوه فعالسازی دکوراتورها را نسبت به کاری که می‌خواهید با تایپ‌اسکریپت انجام دهید، بررسی خواهیم کرد.

کامپایلر CLI تایپ‌اسکریپت

برای فعال‌سازی دکوراتورها در هنگام استفاده از کامپایلر CLI تایپ‌اسکریپت تنها کافیس که از گزینه –experimentalDecorators استفاده کنیم.


tsc --experimentalDecorators

فایل tsconfig.json

در صورت کار با پروژه حاوی فایل tsconfig.json، برای فعال‌سازی دکوراتورهای آزمایشی می‌بایست قابلیت experimentalDecorators را به آبجکت compilerOptions اضافه کنید.


{

"compilerOptions": {

"experimentalDecorators": true

}

}

در محیط آزمایشی تایپ‌اسکریپت، دکوراتورهای به صورت پیش‌فرض فعال هستند.

استفاده از قالب دکوراتور

در این بخش، دکوراتورها را به کلاس‌های تایپ‌اسکریپت اضافه می‌کنیم.

در ابزار تایپ‌اسکریپت، می‌توانید با استفاده از قالب خاص @expression ایجاد کنید. در اینجا، expression تابعی است که در طول زمان اجرا برای هدف دکوراتور فراخوانی می‌شود.

هدف دکوراتور کاملاً به جایی بستگی دارد که آن را اضافه می‌کنید. در حال حاضر، دکوراتورها می‌توانند به اجزای کلاس زیر اضافه شوند.

  • اطلاعات کلاس
  • ویژگی‌ها (Properties)
  • دستیارها (Accessors)
  • متدها
  • پارامترها

به عنوان نمونه، فرض کنید که یک دکوراتور تابع sealed را با ارجاع به Object.seal در یک کلاس فراخوانی کند. برای استفاده از این دکوراتور می‌توانید فرمان زیر را تایپ نمایید.


@sealed

class Person {}

به خاطر داشته باید که دکوراتور درست قبل از هدف  آن، در اینجا کلاس Person، آورده می‌شود.

همین موضوع برای سایر انواع دکوراتورها نیز می‌تواند صادق باشد.


@classDecorator

class Person {

@propertyDecorator

public name: string;

@accessorDecorator

get fullName() {

// ...

}

@methodDecorator

printName(@parameterDecorator prefix: string) {

// ...

}

}

به منظور اضافه‌کردن دکوراتورهای چندگانه، آنها را بلافاصله پس از یکدیگر وارد می‌کنیم.


@decoratorA

@decoratorB

class Person {}

ساخت دکوراتورهای کلاسی در ابزار تایپ‌اسکریپت

در این بخش، به سراغ مراحل ساخت دکوراتورهای کلاسی در تایپ‌اسکریپت می‌رویم. با ما همراه باشید.

برای دکوراتوری با عنوان نمونه @decoratorA، شما به تایپ‌اسکریپت دستور می‌دهید که تابع decorator را فراخوانی کند. تابع decorator همراه جزئیات عملکردش در کد فراخوانی می‌شود. به عنوان مثال، اگر دکوراتور را به یک کلاس اِعمال کرده باشید، تابع جزئیات مربوط به کلاس را دریافت می‌کند. این تابع می‌بایست در حوزه دسترسی دکوراتور شما باشد تا بتواند عملکرد خود را داشته باشد.

برای ساخت یک دکوراتور خاص می‌بایست یک تابع با عنوان دکوراتور ایجاد کنید. بر این اساس، برای ایجاد کلاس sealed که در بخش قبلی دیدیم، بایستی یک تابع sealed با قابلیت دریافت مجموعه‌ای از پارامترها بسازیم. بیایید دقیقاً این کار را انجام دهیم.


@sealed

class Person {}

function sealed(target: Function) {

Object.seal(target);

Object.seal(target.prototype);

}

پارامترهایی که به دکوراتور اختصاص پیدا می‌کنند، به محل استفاده آن بستگی خواهند داشت. اولین پارامتر معمولاً target یا هدف شناخته می‌شود.

دکوراتور sealed تنها برای اطلاعات کلاس استفاده می‌شود. بنابراین تابع شما در انیجا تنها یک پارامتر (target) دریافت می‌کند که از نوع Function خواهد بود. این همان class constructor است که دکوراتور برای آن اِعمال شده است.

در تابع sealed، در ادامه Object.seal برای هدف (class constructor)  و همین طور پروتوتایپ آن فراخوانی می‌شود. در هنگام انجام این کار، هیچ ویژگی دیگری نمی‌تواند به class constructor اضافه شود و ویژگی‌های کنونی نیز به وضعیت «غیرقابل‌تنظیم» درمی‌آیند.

لازم به ذکر است که در این وضعیت و هنگام استفاده از دکوراتورها، امکان اضافه‌کردن نوع تایپ‌اسکریپت وجود ندارد. به این معنا که به عنوان مثال، نمی‌توانید با دکوراتور یک فیلد جدید به کلاس اضافه کرده و آن را در وضعیت type-safe قرار دهید.

اگر یک مقدار را در دکوراتور کلاس sealed برگشت دهید، این مقدار تبدل تابع constructor جدید برای کلاس می‌شود. این موضوع به‌ویژه در هنگامی که می‌خواهید class constructor را به طور کامل بازنویسی کنید، مفید خواهد بود.

تا به اینجا، اولین دکوراتور خود را ایجاد و همراه با یک کلاس استفاده کردید. در بخش بعدی، نحوه ایجاد کارخانه‌های دکوراتور را بررسی می‌کنیم.

ایجاد کارخانه‌های دکوراتور در ابزار تایپ‌اسکریپت

گاهی اوقات در هنگام اِعمال یک دکوراتور، لازم است برخی گزینه‌های اضافی را در آن وارد کنید. برای این منظور، باید از کارخانه‌های دکوراتور استفاده کنید. در این بخ، نحوه ساخت این کارخانه‌ها و استفاده از آنها را فراخواهیم گرفت.

کارخانه‌های دکوراتور توابعی هستند که توابع دیگر را برگشت می‌دهند. از این جهت با عنوان «کارخانه» نامگذاری شده‌اند که عملکردی از خودِ دکوراتور محسوب نمی‌شوند. به جای آن، آنها تابع دیگری را برگشت می‌دهند که مسئول به‌کارگیری دکوراتور است و به عنوان یک تابع پوششی عمل می‌کنند. این کارخانه‌ها برای تولید دکوراتورهای سفارشی بسیار مفید هستند؛ چرا که به کدهای کلاینت این امکان را می‌دهند که در هنگام استفاده از دکوراتورها، از گزینه‌های آنها استفاده کنند.

فرض کنید که یک دکوراتور کلاس با عنوان decoratorA دارید و می‌خواهید یک گزینه قابل‌تنظیم در هنگام فراخوانی مانند Boolean به آن اضافه کنید. برای این منظور می‌توانید یک کارخانه دکوراتور مانند زیر بنویسید.


const decoratorA = (someBooleanFlag: boolean) => {

return (target: Function) => {

}

}

در اینجا تابع decoratorA یک تابع دیگر را برای بکارگیری دکوراتور فراخوانی می‌کند. دقت کنید که کارخانه دکوراتور گزینه boolean را به عنوان تنها پارامتر خود دریافت می‌کند.


const decoratorA = (someBooleanFlag: boolean) => {

return (target: Function) => {

}

}

در هنگام استفاده از دکوراتور، امکان مشخص‌کردن مقدار این پارامتر وجود خواهد داشت. این موضوع را در کد زیر مشاهده می‌کنید.


const decoratorA = (someBooleanFlag: boolean) => {

return (target: Function) => {

}

}

@decoratorA(true)

class Person {}

در اینجا وقتی از دکوراتور decorator استفاده می‌کنیم، کارخانه دکوراتور  با پارامتر someBooleanFlag با مقدار true فراخوانی می‌شود. سپس خودِ دکوراتور اجرا می‌شود. در نتیجه می‌توانید بر اساس نحوه کاربرد دکوراتور، رفتار آن را تغییر دهید. در این حال، دکوراتورها به راحتی می‌توانند شخصی‌سازی شده و دوباره مورد استفاده قرار گیرند.

به خاطر داشته باشید که می‌بایست تمام پارامتر مورد انتظار کارخانه دکوراتور را وارد کنید. در صورتی که مطابق زیر حتی یکی از پارامترها را از قلم بیندازید:


const decoratorA = (someBooleanFlag: boolean) => {

return (target: Function) => {

}

}

@decoratorA

class Person {}

کامپایلر ابزار تایپ‌اسکریپت دو خطا برایتان نمایش خواهد داد. این خطاها بسته به نوع دکوراتور شما متفاوت خواهند بود. برای دکوراتورهای class این خطاها 1238 و 1240 هستند.

خروجی


Unable to resolve signature of class decorator when called as an expression.

Type '(target: Function) => void' is not assignable to type 'typeof Person'.

Type '(target: Function) => void' provides no match for the signature 'new (): Person'. (1238)

Argument of type 'typeof Person' is not assignable to parameter of type 'boolean'. (2345)

در اینجا یک کارخانه دکوراتور ایجاد کردیم که می‌تواند پارامترها را دریافت کند و رفتار دکوراتورها را بر اساس این پارامترها تغییر دهد. در مرحله بعدی نحوه ساخت دکوراتورهای property را فراخواهیم گرفت.

ساخت دکوراتورهای Property

مشخصات کلاس‌ها جای دیگری است که می‌توانید از دکوراتورها استفاده کنید. در این بخش نگاهی خواهیم داشت به نحوه ساخت این نوع دکوراتورها.

هر دکوراتور property، پارامترهای زیر را دریافت می‌کند.

  • برای مشخصات استاتیک، تابع constructor و برای سایر مشخصات، prototype مربوط به کلاس.
  • نام عضو کلاس.

در حال حاضر، هیچ راهی برای دسترسی به property descriptor به عنوان یک پارامتر نیست. این موضوع به دلیل روشی است که دکوراتورهای property در تایپ‌اسکریپت راه‌اندازی می‌شوند.

در اینجا یک تابع دکوراتور را می‌بینیم که عنوان عضو کنسول را چاپ می‌کند.


const printMemberName = (target: any, memberName: string) => {

console.log(memberName);

};

class Person {

@printMemberName

name: string = "Jon";

}

وقتی کد تایپ‌اسکریپت بالا را اجرا می‌کنید، چاپ زیر را در کنسول مشاهده خواهید کرد.


name

می‌توانید از دکوراتورهای property برای انجام تغییرات استفاده کنید. این کار می‌تواند با کمک Object.defineProperty همراه با یک تنظیم‌ و دریافت‌کننده جدید برای property انجام گیرد. در اینجا نحوه ساخت یک دکوراتور با عنوان allowlist را می‌بینیم. این دکوراتور به یک property اجازه می‌دهد که فقط به مقادیر موجود در لیست استاتیک allowlist تنظیم شود.


const allowlist = ["Jon", "Jane"];

const allowlistOnly = (target: any, memberName: string) => {

let currentValue: any = target[memberName];

Object.defineProperty(target, memberName, {

set: (newValue: any) => {

if (!allowlist.includes(newValue)) {

return;

}

currentValue = newValue;

},

get: () => currentValue

});

};

در ابتدا یک لیست استاتیک allowlist در بالای کد ایجاد می‌کنیم.


const allowlist = ["Jon", "Jane"];

سپس نوبت به بکارگیری دکوراتور property می‌رسد.

const allowlistOnly = (target: any, memberName: string) => {

let currentValue: any = target[memberName];

Object.defineProperty(target, memberName, {

set: (newValue: any) => {

if (!allowlist.includes(newValue)) {

return;

}

currentValue = newValue;

},

get: () => currentValue

});

};

دقت داشته باشید که نوع هدف یا target به صورت any تنظیم می‌شود.


const allowlistOnly = (target: any, memberName: string) => {

در دکوراتورهای property، نوع پارامتر هدف می‌تواند به صورت constructor یا prototype باشد. در این حالت، بهتر است که از مقدار any استفاده شود.

در ابتدای بکارگیری دکوراتور، مقدار کنونی property را به متغیر currentValue تنظیم می‌کنید.


let currentValue: any = target[memberName];

برای تنظیمات استاتیک، این کار باعث تنظیم مقدار پیش‌فرض می‌شود. ولی برای تنظیمات غیر استاتیک، این متغیر همیشه به صورت تعریف نشده است. این موضوع به این دلیل است که در ران‌تایم، در کد جاوا اسکریپت کامپایل شده، دکوراتور قبل از  تنظیم property به مقدار پیش‌فرض، اجرا می‌شود.

سپس با استفاده از Object.defineProperty، مقدار property بازنویسی می‌شود.


Object.defineProperty(target, memberName, {

set: (newValue: any) => {

if (!allowlist.includes(newValue)) {

return;

}

currentValue = newValue;

},

get: () => currentValue

});

فراخوانی Object.defineProperty دارای یک دریافت‌کننده و یک تنظیم‌کننده است. دریافت‌کننده مقدار ذخیره‌شده در متغیر currentValue را برگشت می‌دهد. تنظیم‌کننده مقدار currentValue را با بررسی لیست allowlist  به newValue تغییر می‌دهد.

حالا از دکوراتوری که در اینجا ساخته‌ایم، استفاده می‌کنیم. برای این منظور، کلاس Person را مطابق زیر ایجاد کنید.


class Person {

@allowlistOnly

name: string = "Jon";

}

اکنون یک نسخه جدید از کلاس ایجاد و property دریافت و تنظیم نام را آزمایش می‌کنید.


const allowlist = ["Jon", "Jane"];

const allowlistOnly = (target: any, memberName: string) => {

let currentValue: any = target[memberName];

Object.defineProperty(target, memberName, {

set: (newValue: any) => {

if (!allowlist.includes(newValue)) {

return;

}

currentValue = newValue;

},

get: () => currentValue

});

};

class Person {

@allowlistOnly

name: string = "Jon";

}

const person = new Person();

console.log(person.name);

person.name = "Peter";

console.log(person.name);

person.name = "Jane";

console.log(person.name);

با اجرای کد می‌بایست با خروجی زیر روبرو شوید.


Jon

Jon

Jane

مقدار هیچ‌وقت به نام Peter تنظیم نمی‌شود. چرا که در لیست allowlist وجود ندارد.

امّا در صورتی که بخواهیم این کد کاربردهای دیگری نیز داشته باشد، چه باید کرد؟ اینکه امکان تنظیم لیست allowlist در هنگام اجرای دکوراتور وجود داشته باشد. این در واقع، یکی از بهترین موارد استفاده کارخانه‌های دکوراتور است. این موضوع را در قالب تبدیل دکوراتور allowlistOnly به یک کارخانه دکوراتور بررسی می‌کنیم.


const allowlistOnly = (allowlist: string[]) => {

return (target: any, memberName: string) => {

let currentValue: any = target[memberName];

Object.defineProperty(target, memberName, {

set: (newValue: any) => {

if (!allowlist.includes(newValue)) {

return;

}

currentValue = newValue;

},

get: () => currentValue

});

};

}

در اینجا شما کاربری قبلی را در پوشش یک تابع دیگر، و به عبارتی کارخانه دکوراتور قرار می‌دهید. کارخانه دکوراتور یک پارامتر با عنوان allowlist دریافت می‌کند که شامل یک سری رشته‌ای است.

حالا برای استفاده از دکوراتور، باید از allowlist مطابق زیر استفاده کنید.


class Person {

@allowlistOnly(["Claire", "Oliver"])

name: string = "Claire";

}

سعی کنید که کد مشابه قبلی و با تغییرات جدید را اجرا کنید.


const allowlistOnly = (allowlist: string[]) => {

return (target: any, memberName: string) => {

let currentValue: any = target[memberName];

Object.defineProperty(target, memberName, {

set: (newValue: any) => {

if (!allowlist.includes(newValue)) {

return;

}

currentValue = newValue;

},

get: () => currentValue

});

};

}

class Person {

@allowlistOnly(["Claire", "Oliver"])

name: string = "Claire";

}

const person = new Person();

console.log(person.name);

person.name = "Peter";

console.log(person.name);

person.name = "Oliver";

console.log(person.name);

این کد می‌بایست خروجی زیر را به شما نتیجه بدهد.


Claire

Claire

Oliver

این خروجی نشانه این است که کد مطابق انتظار عمل کرده و مقدار person.name هیچ‌وقت به Peter تنظیم نمی‌شود. چرا که Peter در لیست allowlist حضور ندارد.

ساخت دکوراتورهای Accessor

در این بخش، نگاهی خواهیم داشت به نحوه دکوراتورهای Accessor در کلاس‌ها. همانند دکوراتورهای property، دکوراتورهای مورد استفاده در یک accessor پارامترهای زیر را دریافت می‌کنند:

  • برای مشخصات استاتیک، تابع constructor و برای سایر مشخصات، prototype مربوط به کلاس.
  • نام عضو کلاس.

امّا برخلاف دکوراتور property، یک پارامتر سوم با عنوان Property Descriptor برای عضو accessor وجود دارد.

با توجه به اینکه Property Descriptor شامل هر دو آیتم تنظیم‌کننده و دریافت‌کننده برای یک عضو کلاس خاص هستند، دکوراتورهایی از این نوع تنها می‌توانند برای تنظیم‌کننده یا دریافت‌کننده یک عضو، و نه هر دو به صورت همزمان اِعمال شوند.

در صورتی که یک مقدار از دکوراتور accessor برگشت داده باشید، این مقدار تبدیل به Property Descriptor جدید هر دو اعضای دریافت‌کننده و تنظیم‌کننده کلاس می‌شود.

در اینجا مثالی از کاربرد یک دکوراتور برای تغییر گزینه enumerable در یک accessor دریافت‌کننده/ تنظیم‌کننده را مشاهده می‌کنید.


const enumerable = (value: boolean) => {

return (target: any, memberName: string, propertyDescriptor: PropertyDescriptor) => {

propertyDescriptor.enumerable = value;

}

}

به کاربرد یک کارخانه دکوراتور در اینجا دقت کنید. این به شما امکان می‌دهد که در هنگام فراخوانی دکوراتور، گزینه enumerable را مشخص کنید. در زیر نحوه استفاده از دکوراتور را می‌بینید.


class Person {

firstName: string = "Jon"

lastName: string = "Doe"

@enumerable(true)

get fullName () {

return `${this.firstName} ${this.lastName}`;

}

}

دکوراتورهای Accessor بسیار شبیه به دکوراتورهای property هستند. تنها تفاوت در دریافت پارامتر سوم property descriptor است. در بخش بعدی به نحوه ایجاد دکوراتورهای method خواهیم پرداخت

ساخت دکوراتورهای Method

در این بخش، نحوه استفاده از دکوراتورهای method را بررسی می‌کنیم.

بکارگیری دکوراتورهای method بسیار شبیه به روش ایجاد دکوراتورهای accessor است. پارامترهای ارائه‌شده به دکوراتور مشابه آنهایی هستند که برای دکوراتورهای accessor استفاده می‌شوند.

بیایید از همان دکوراتور enumerable قبلی در اینجا استفاده کنیم. ولی این بار با متد getFullName از کلاس Person زیر:


const enumerable = (value: boolean) => {

return (target: any, memberName: string, propertyDescriptor: PropertyDescriptor) => {

propertyDescriptor.enumerable = value;

}

}

class Person {

firstName: string = "Jon"

lastName: string = "Doe"

@enumerable(true)

getFullName () {

return `${this.firstName} ${this.lastName}`;

}

}

در صورتی که یک مقدار از دکوراتور method برگشت داده باشید، این مقدار تبدیل به Property Descriptor جدید متد خواهد شد.

در اینجا می‌خواهیم به عنوان نمونه یک دکوراتور deprecated ایجاد کنیم. این دکوراتور پیام‌های ارائه شده به کنسول را در زمان استفاده از متد، چاپ  و یک پیام حاکی از «منسوخ‌بودن متد» وارد می‌کند.


const deprecated = (deprecationReason: string) => {

return (target: any, memberName: string, propertyDescriptor: PropertyDescriptor) => {

return {

get() {

const wrapperFn = (...args: any[]) => {

console.warn(`Method ${memberName} is deprecated with reason: ${deprecationReason}`);

propertyDescriptor.value.apply(this, args)

}

Object.defineProperty(this, memberName, {

value: wrapperFn,

configurable: true,

writable: true

});

return wrapperFn;

}

}

}

}

در اینجا یک دکوراتور با استفاده از کارخانه دکوراتور ایجاد می‌کنید. این کارخانه دکوراتور یک آرگومان رشته‌ای دریافت می‌کند که دلیلی برای منسوخ‌شدن خواهد بود.


const deprecated = (deprecationReason: string) => {

return (target: any, memberName: string, propertyDescriptor: PropertyDescriptor) => {

// ...

}

}

متغیر deprecationReason بعداً در هنگام وارد کردن «پیام مربوط به منسوخ‌شدن» در کنسول مورد استفاده قرار می‌گیرد. در هنگام استفاده از دکوراتور deprecated، شما یک مقدار را برگشت می‌دهید. وقتی یک مقدار را از یک دکوراتور method برگشت می‌دهید، این مقدار در Property Descriptor عضو کلاس بازنویسی می‌شود.

به این ترتیب، یک «دریافت‌کننده» به متد «دکور شده» کلاس خود می‌افزایید. در نتیجه، نحوه استفاده از متد را تغییر می‌دهید.

امّا چرا فقط از Object.defineProperty به جای برگشت یک دکوراتور property برای متد استفاده نکنیم؟ این موضوع از آنجا اهمیت دارد که شما می‌بایست برای متدهای کلاس غیر استاتیک، به این مقدار دسترسی داشته باشید. اگر مستقیماً از Object.defineProperty استفاده کنید، هیچ راهی برای استخراج این مقدار برای شما وجود نخواهد داشت. همچنین اگر از متد به این صورت استفاده شود، در هنگام اجرای متد پوشی درون دکوراتور، دکوراتور کد شما را متوقف خواهد کرد.

درون دریافت‌کننده، یک تابع پوششی محلی با نام wrapperFn ایجاد می‌کنیم. این تابع  با استفاده از console.warn و انتقال deprecationReason از کارخانه کوراتور، یک پیام را به کنسول وارد می‌کند. سپس متد اصلی با استفاده propertyDescriptor.value.apply(this, args) فراخوانی می‌شود. به این ترتیب، متد اصلی با توجه به مقدار درست مرتبط با کلاس در هنگام استفاده از یک متد غیر استاتیک فراخوانی خواهد شد.

سپس از defineProperty برای بازنویسی مقدار متد در کلاس استفاده می‌شود. این بخش  همانند یک ساز و کار «به خاطر سپاری» عمل می‌کند. چرا که چندین فراخوانی برای یک متد، موجب فراخوانی «دریافت‌کننده» شما نمی‌شود و مستقیماً wrapperFn را فراخوانی می‌کند. در حال حاضر، شما عضو کلاس را برای داشتن مقدار برابر wrapperFn با استفاده از Object.defineProperty تنظیم می‌کنید.

بیایید دوباره از دکوراتور deprecated استفاده کنیم.


const deprecated = (deprecationReason: string) => {

return (target: any, memberName: string, propertyDescriptor: PropertyDescriptor) => {

return {

get() {

const wrapperFn = (...args: any[]) => {

console.warn(`Method ${memberName} is deprecated with reason: ${deprecationReason}`);

propertyDescriptor.value.apply(this, args)

}

Object.defineProperty(this, memberName, {

value: wrapperFn,

configurable: true,

writable: true

});

return wrapperFn;

}

}

}

}

class TestClass {

static staticMember = true;

instanceMember: string = "hello"

@deprecated("Use another static method")

static deprecatedMethodStatic() {

console.log('inside deprecated static method - staticMember =', this.staticMember);

}

@deprecated("Use another instance method")

deprecatedMethod () {

console.log('inside deprecated instance method - instanceMember =', this.instanceMember);

}

}

TestClass.deprecatedMethodStatic();

const instance = new TestClass();

instance.deprecatedMethod();

در اینجا یک کلاس نمونه TestClass با دو ویژگی ایجاد می‌کنیم؛ استاتیک و غیر استاتیک. همچنین دو متد استاتیک و غیر استاتیک برای این کلاس درنظر گرفته شده است.

سپس دکوراتور deprecated را برای هر دو متد اِعمال می‌کنیم. در هنگام اجرای کد، موارد زیر در کنسول نمایش داده خواهد شد.


(warning) Method deprecatedMethodStatic is deprecated with reason: Use another static method

inside deprecated static method - staticMember = true

(warning)) Method deprecatedMethod is deprecated with reason: Use another instance method

inside deprecated instance method - instanceMember = hello

این خروجی نشان می‌دهد که هر دو متد توسط تابع پوششی پوشش داده شده‌اند.

هم‌اکنون اولین دکوراتور method خود را با استفاده از ابزار تایپ‌اسکریپت ایجاد کرده‌اید. در بخش بعدی نحوه ایجاد آخرین نوع دکوراتور پشتیبانی‌شونده توسط ابزار تایپ‌اسکریپت، یعنی دکوراتور parameter را بررسی می‌کنیم.

ایجاد دکوراتورهای پارامتری

دکوراتورهای پارامتری می‌توانند برای پارامترهای متدهای کلاس‌ها استفاده شوند. در این بخش، نحوه ساخت این نوع دکوراتورها را فراخواهیم گرفت.

تابع دکوراتور مورد استفاده با پارامترها، موارد زیر را دریافت می‌کند.

  • برای مشخصات استاتیک، تابع constructor مربوط به کلاس. برای سایر مشخصات، prototype کلاس.
  • نام عضو کلاس
  • شناسه پارامتر در لیست پارامترهای متد.

امکان تغییرات مرتبط با پارامترها وجود ندارد. بنابراین چنین دکوراتورهایی تنها برای مشاهده عملکرد پارامترها مفید خواهند بود. مگر اینکه از آیتم‌های پیشرفته‌تر مانند reflect-metadata استفاده کرده باشید.

در اینجا مثالی از یک دکوراتور که شناسه پارامتر «دکور شده» را همراه با نام متد چاپ می‌کند، مشاهده می‌کنید.


function print(target: Object, propertyKey: string, parameterIndex: number) {

console.log(`Decorating param ${parameterIndex} from ${propertyKey}`);

}

سپس می‌توانید به صورت زیر از دکوراتور پارامتری به صورت زیر استفاده کنید.


class TestClass {

testMethod(param0: any, @print param1: any) {}

}

اجرای کد بالا موجب نمایش خروجی زیر در کنسول خواهد شد.


Decorating param 1 from testMethod

جمع‌بندی

در این آموزش، تمام انواع دکوراتورهای پشتیبانی‌شونده توسط ابزار تایپ‌اسکریپت را بررسی کردیم. این دکوراتورها همراه با کلاس‌ها استفاده شدند و به تفاوت‌های آنها نیز اشاره شد. اکنون می‌توانید با نوشتن دکوراتورهای خود، از حجم کد پایه کم کنید و یا اینکه از آنها در کنار کتابخانه‌هایی مانند Mobx استفاده کنید.