اولویت Decorator ها و آشنایی با property decorator

Priority of Decorators and Familiarity with Property Decorator

14 مرداد 1399
اولویت Decorator ها و آشنایی با property decorator

اولویت Decorator ها

در جلسه قبل با Decorator ها و decorator factory ها آشنا شدید و حالا می دانید که چه قابلیت هایی را به ما می دهند اما نکات زیادی از decorator ها باقی مانده است. مثلا شما می توانید بیشتر از یک decorator داشته باشید و همزمان از آن ها استفاده کنید. به طور مثال:

@Logger('LOGGING')
@WithTemplate('<h1>My Person Object</h1>', 'app')
class Person {
  name = 'Max';

  constructor() {
    console.log('Creating person object...');
  }
}

با اجرای کد بالا هر دو Decorator ما اجرا شده و در مرورگر چنین صحنه ای را می بینیم:

نتیجه ی اجرای چندین Decorator
نتیجه اجرای چندین Decorator

یعنی همزمان نام Max در خود مرورگر و LOGGING نیز در کنسول آن قابل مشاهده است. اینجا سوالی برای ما پیش می آید و آن این است که decorator های چند تایی مانند مثال بالا با چه ترتیبی اجرا می شوند؟ کدام یک ابتدا و کدام یک بعد از آن اجرا خواهد شد؟ برای پاسخ به این سوال بهتر است یک پیام log نیز در withTemplate قرار بدهیم:

function WithTemplate(template: string, hookId: string) {
  return function(constructor: any) {
    console.log('Rendering template');
    const hookEl = document.getElementById(hookId);
    const p = new constructor();
    if (hookEl) {
      hookEl.innerHTML = template;
      hookEl.querySelector('h1')!.textContent = p.name;
    }
  }
}

اگر به مرورگر بروید مشاهده خواهید کرد که rendering template اول از همه اجرا می شود و پس از آن LOGGING را خواهیم داشت. از این موضوع نتیجه می گیریم که decorator ها از پایین به بالا اجرا می شوند. به کد زیر نگاه کنید:

@Logger('LOGGING')
@WithTemplate('<h1>My Person Object</h1>', 'app')
class Person {
  name = 'Max';

  constructor() {
    console.log('Creating person object...');
  }
}

در این کد هر Decorator ای که پایین تر باشد زودتر اجرا می شود (از پایین به بالا) بنابراین ابتدا WithTemplate و سپس Logger اجرا خواهد شد. البته حواستان باشد که ما داریم در رابطه با خود decorator ها یا همان decorator function صحبت می کنیم نه در مورد decorator factory ها! در واقع decorator factory ها قبل از همه اجرا می شوند و برای ثابت کردن این موضوع می توانیم یک دستور log را نیز درون آن ها قرار دهیم:

function Logger(logString: string) {
  console.log('LOGGER FACTORY');
  return function(constructor: Function) {
    console.log(logString);
    console.log(constructor);
  };
}

من دستور چاپ LOGGER FACTORY را به decorator factory بالا اضافه کرده ام. حالا همین کار را برای WithTemplate نیز انجام می دهم:

function WithTemplate(template: string, hookId: string) {
  console.log('TEMPLATE FACTORY');
  return function(constructor: any) {
    console.log('Rendering template');
    const hookEl = document.getElementById(hookId);
    const p = new constructor();
    if (hookEl) {
      hookEl.innerHTML = template;
      hookEl.querySelector('h1')!.textContent = p.name;
    }
  }
}

در اینجا دستور چاپ TEMPLATE FACTORY را به تابع خود اضافه کرده ام. حالا اگر کدها را ذخیره کرده و به مرورگر بروید متوجه ترتیب اجرای آن ها می شوید:

ترتیب اجرای Decorator ها و decorator factory ها
ترتیب اجرای Decorator ها و decorator factory ها

بنابراین از بین دو decorator factory ابتدا Logger و سپس WithTemplate اجرا شده (از بالا به پایین) و از بین دو decorator function نیز ابتدا WithTemplate و سپس Logger اجرا شده اند (از پایین به بالا). دلیل این مسئله این است که در هنگام انتساب decorator function ها به یک کلاس، آن ها را صدا می زنیم و ترتیب صدا زدن آن ها مانند ترتیب صدا زدن هر تابع دیگری در جاوا اسکریپت است.

آشنایی با property decorator

تا اینجا متوجه شدیم که می توانیم decorator ها را به کل یک کلاس انتساب دهیم اما قسمت های دیگری نیز وجود دارد که می توانیم از آن ها استفاده کنیم. برای شروع یک کلاس جدید تعریف می کنم. تمام Decorator ها با کلاس ها کار می کنند بنابراین به یک کلاس نیاز داریم:

class Product {
  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);
  }
}

این کلاس یک خصوصیت public به نام title و یک خصوصیت private به نام price_ دارد. متد Set یک setter است و به ما اجازه می دهد خصوصیت price را از خارج از کلاس تغییر دهیم. البته شرط ما این است که عدد وارد شده برای price حتما از صفر بزرگتر باشد تا قیمت منفی نداشته باشیم. constructor نیز خصوصیات title و price را مقداردهی اولیه می کند و متد getPriceWithTax به صورت فرضی مالیات کالا را حساب می کند که یک فرمول ریاضی ساده و فرضی است.

من می خواهم برای اکثر قسمت های این کلاس decorator های جداگانه تعریف کنم بنابراین ابتدا با همان decorator همیشگی خودمان برای log کردن شروع می کنیم. همانطور که گفتم اینکه هر decorator چه آرگومان هایی می گیرد، بستگی به این دارد که بخواهید آن را به کدام قسمت اضافه کنید. مثلا در جلسه قبل یک decorator به نام logger داشتیم که constructor را به عنوان آرگومان دریافت می کرد و دلیلش هم این بود که آن را مستقیما به کل کلاس انتساب داده بودیم اما من می خواهم تابع log جدید خود را فقط به خصوصیت های کلاس بالا اضافه کنم.

زمانی که بخواهید یک decorator را به یک property اضافه کنید، باید دو آرگومان را به آن پاس بدهید: ابتدا target است که در کلاس ما از نوع Any است چرا که هنوز نمی دانیم خصوصیت ما از چه نوعی خواهد بود. اگر خصوصیت ما static بود می توانیم نوع آن را به جای Any روی constructor قرار دهیم. این target همان prototype کلاس ما خواهد بود. دومین آرگومان نیز نام خصوصیت است که می تواند یک رشته یا یک symbol باشد. بنابراین:

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

حالا این decorator را به خصوصیت title اضافه می کنیم:

class Product {
  @Log
  title: string;
  private _price: number;
// بقیه کدها //

من در این قسمت دو دستور console.log را نوشته ام تا نتیجه را در مرورگر ببینیم:

لاگ شدن property decorator ما
لاگ شدن property decorator ما

اولین دستور log به سادگی مشخص است و همانطور که گفتم دستور دوم نیز همان prototype کلاس ما به همراه نام خصوصیت (title) است. این یک property decorator است. ما در قسمت بعدی decorator های بیشتری را به قسمت های دیگری از این کلاس اضافه خواهیم کرد.

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

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

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

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