اضافه کردن Decorator به قسمت‌های دیگر کلاس

Adding Decorator to Other Parts of the Class

14 مرداد 1399
اضافه کردن decorator به قسمت های دیگر کلاس

در قسمت قبل توانستیم decorator خود را به یک خصوصیت اضافه کنیم و حالا نوبت اضافه کردن آن به دیگر اجزای کلاس است. در ابتدای کار باید decorator جدید خود را خارج از تابع تعریف کنیم تا آن را به accessor (در اینجا همان setter ای که تعریف کردیم) متصل کنیم. من ابتدا کلاس خودم را دوباره برای شما قرار می دهم تا به آن نگاهی بیندازید:

class Product {
  @Log
  title: string;
  private _price: number;

  set price(val: number) {
    if (val > 0) {
      this._price = val;
    } else {
      throw new Error('Invalid price - should be positive!');
    }
  }

  constructor(t: string, p: number) {
    this.title = t;
    this._price = p;
  }

  getPriceWithTax(tax: number) {
    return this._price * (1 + tax);
  }
}

ما این کلاس را در جلسه قبل نوشتیم و یک decorator را به property خود یعنی title اضافه کردیم اما به شما گفتم که این تنها محل استفاده از decorator ها نیست، بنابراین می خواهیم امروز آن ها را به accessor ها و متدها و پارامتر ها نیز اضافه کنیم. decorator جلسه قبل Log نام داشت بنابراین برای decorator بعدی خود از نام Log2 استفاده می کنم:

function Log(target: any, propertyName: string | Symbol) {
  console.log('Property decorator!');
  console.log(target, propertyName);
}

function Log2(target: any, name: string, descriptor: PropertyDescriptor) {
  console.log('Accessor decorator!');
  console.log(target);
  console.log(name);
  console.log(descriptor);
}

این همان decorator جدیدی است که log2 نام دارد. توجه داشته باشید که PropertyDescriptor یکی از تایپ های تایپ اسکریپت است. حالا این decorator را به Setter خود متصل می کنیم:

class Product {
  @Log
  title: string;
  private _price: number;

  @Log2
  set price(val: number) {
    if (val > 0) {
      this._price = val;
    } else {
      throw new Error('Invalid price - should be positive!');
    }
  }

// بقیه کدها //

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

خروجی setter decorator در کنسول مرورگر
خروجی setter decorator در کنسول مرورگر

در ابتدا عبارت Access decorator را داریم، سپس prototype خود را داریم، سپس name یا نام آن setter را داریم که price بود و در نهایت PropertyDescriptor را داریم که setter ما را توصیف می کند. جدا از این accessor ما متدهایی را نیز در کلاس خود داریم بنابراین یک Decorator دیگر برای آن ها تعریف می کنم:

function Log3(
  target: any,
  name: string | Symbol,
  descriptor: PropertyDescriptor
) {
  console.log('Method decorator!');
  console.log(target);
  console.log(name);
  console.log(descriptor);
}

باز هم با سه آرگومان قبلی مواجه هستیم و همانطور که گفتم این آرگومان ها دلخواه نیستند، بلکه بستگی دارند decorator را کجا قرار دهید. از آنجایی که می خواهیم این decorator را روی یک متد قرار دهیم می دانیم که این سه آرگومان را خواهیم داشت. حالا این decorator را به متد خودمان متصل می کنیم:

class Product {
  @Log
  title: string;
  private _price: number;

  @Log2
  set price(val: number) {
    if (val > 0) {
      this._price = val;
    } else {
      throw new Error('Invalid price - should be positive!');
    }
  }

  constructor(t: string, p: number) {
    this.title = t;
    this._price = p;
  }

  @Log3
  getPriceWithTax(tax: number) {
    return this._price * (1 + tax);
  }
}

با ذخیره و اجرای این کد در کنسول مرورگر شاهد چنین مقادیری هستیم:

خروجی method decorator در کنسول مرورگر
خروجی method decorator در کنسول مرورگر

دقیقا همان منطق قبلی را در اینجا نیز شاهد هستیم و همه چیز به درستی کار می کند. آخرین decorator ای که می توانیم اضافه کنیم، اضافه کردن به یک پارامتر است بنابراین decorator چهارم را نیز تعریف می کنم تا آن را به پارامتر tax اضافه کنیم:

function Log4(target: any, name: string | Symbol, position: number) {
  console.log('Parameter decorator!');
  console.log(target);
  console.log(name);
  console.log(position);
}

Decorator هایی که مخصوص پارامتر هستند سه آرگومان دریافت می کنند، اولی همان target و دومی نام متدی است که پارامتر در آن استفاده می شود و سومی (position) مشخص می کند که آرگومان ما چندمین آرگومان است. حالا این decorator را به پارامتر متد مورد نظر متصل می کنیم:

  @Log3
  getPriceWithTax(@Log4 tax: number) {
    return this._price * (1 + tax);
  }

با اجرای این دستور، مقادیر زیر را در کنسول مرورگر مشاهده خواهیم کرد:

خروجی parameter decorator در کنسول مرورگر
خروجی parameter decorator در کنسول مرورگر

عدد صفر که مشاهده می کنید همان position است (ایندکس صفر یعنی اولین پارامتر). همانطور که مشاهده می کنید decorator مربوط به پارامتر ما قبل از decorator مربوط به متد چاپ شده است چرا که اجرا شدن decorator ها دارای ترتیب و اولویت خاصی است و همیشه از بالا به پایین اجرا نمی شود. نکته بعد اینجاست که باید بدانید تمام این decorator ها بدون اینکه شیء ای را از این کلاس بسازیم اجرا می شوند. به زبان دیگر اگر من این کلاس را به صورت زیر instantiate کنم:

const p1 = new Product('Book', 19);
const p2 = new Product('Book 2', 29);

هیچ خطایی دریافت نمی کنیم اما decorator جدیدی نیز چاپ نمی شود و همان decorator های قبلی را در کنسول مرورگر خواهیم داشت. همیشه به یاد داشته باشید که decorator های شما (چه از نوع property decorator چه از نوع method decorator و الی آخر) به محض تعریف شدن کلاس اجرا می شوند. decorator ها با instantiate (ساخته شدن شیء از کلاس) اجرا نمی شوند، به دلیل اینکه قرار نیست کار decorator ها مانند توابع یا event-listener ها باشد بلکه وظیفه decorator ها نوشتن منطق اضافی برای اجرا شدن در پشت صحنه برنامه است. ما در این جلسه از console.log استفاده کردیم اما شما می توانید کدهای دیگری را درون آن بنویسید، مثلا داده هایی را در قسمت دیگری از برنامه تان ذخیره کنید.

همچنین همانطور که قبلا هم گفته بودم برای استفاده از decorator ها حتما باید آن ها را از فایل tsconfig.json فعال نمایید:

"experimentalDecorators": true

در جلسه بعد با مثالی واقعی تر از decorator ها برای تغییر ساختار یک کلاس مواجه می شویم.

دانلود سورس کد این جلسه

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش جامع تایپ اسکریپت توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

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

مقالات مرتبط
ما را دنبال کنید
اینستاگرام روکسو تلگرام روکسو ایمیل و خبرنامه روکسو