دکوراتورهای تایپاسکریپت: چرا برخی دکوراتورها نیاز به پرانتز دارند و برخی دیگر ندارند؟


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

در این مثال چرا decorator1() هنگام استفاده روی متد یک کلاس نیاز به پرانتز دارد اما decorator2 اینطور نیست؟ دانش من از دکوراتورهای TS هنوز کافی نیست تا تفاوت بین دو نوع دکوراتور را تشخیص دهم، بنابراین هرگونه راهنمایی مورد قدردانی است.

https://codesandbox.io/s/typescript-decorator-forked-v3u6q?file=/src/index.ts

function decorate1() {
  console.log("decorate1(): factory evaluated");
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    console.log("decorate1(): called");
  };
}

function decorate2(target, key, descriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    // Call the original method
    console.log("calling decorate2 function");
    const result = original.apply(this, args);
    console.log("decorate2 returned", result);
    return result;
  };
  return descriptor;
}

class ExampleClass {
  @decorate1()
  @decorate2
  method() {
    return "something";
  }
}

const example = new ExampleClass();
example.method();

پاسخ شماره ۱

در ابتدا مهم است به خاطر داشته باشید که دکوراتورها یک ویژگی آزمایشی در تایپاسکریپت هستند، و ویژگی متناظر پیشنهادی جاوااسکریپت تغییرات قابل توجهی داشته و در مرحله ۲ فرآیند پیشنهاد TC39 قرار دارد. از این رو، احتمالاً بهتر است از آنها اجتناب شود اگر هنوز شروع به استفاده از آنها نکردهاید. اگر و وقتی که پیشنهاد دکوراتورها به مرحله ۳ برسد، تایپاسکریپت ویژگی TS را برای تطابق با آن اصلاح خواهد کرد، که میتواند یک تغییر شکستندهنده باشد.


خلاصه: دکوراتورها توابعی هستند که برخی آرگومانها را میگیرند و مقادیری مشخص را باز میگردانند؛ شما میتوانید با هر عبارتی دکور کنید تا زمانی که به عنوان دکوراتور عمل کند. پرانتزها بخشی از نحو اعلان دکوراتور نیستند؛ آنها بخشی از عبارت هستند. اگر بنویسید @foo در این صورت شما از foo به عنوان دکوراتور استفاده میکنید. اگر بنویسید @bar() در این صورت شما از bar()، نه bar، به عنوان دکوراتور استفاده میکنید. این دقیقاً مثل این است که const baz=bar() نوشتهاید و سپس با @baz دکور کردهاید.


به هر حال، شما در مورد دکوریتورهای متد صحبت میکنید. یک دکوریتور متد تابعی است که سه آرگومان میگیرد: نمونهٔ پروتوتایپ کلاس یا سازنده (بسته به static بودن متدی که دکور میشود)؛ نام متد، و توصیفکنندهٔ ویژگی برای متد. و یا هیچ چیزی برنمیگرداند، یا یک توصیفکنندهٔ ویژگی جدید باز میگرداند.

پس این یک دکوریتور متد است:

const decorator = (
    target: any, key: PropertyKey, descriptor: PropertyDescriptor
) => {
    console.log("decorator", key);
}

شما یک متد کلاس را با قرار دادن اعلامیه دکوراتور درست قبل از اعلامیه متد تزئین میکنید. یک اعلامیه دکوراتور به شکل @ به دنبال یک عبارت دکوراتور است. این میتواند هر عبارتی باشد تا زمانی که بتواند به عنوان دکوراتور عمل کند؛ در این مورد، یک تابع که مطابق با قاعده بالا باشد:

class Foo {
    @decorator
    method1() { } //"decorator",  "method1" 
} 

شما با عبارت decorator تزئین میکنید. توجه داشته باشید که بعد از decorator پرانتز وجود ندارد. اگر شما مینوشتید decorator()، شما در واقع فراخوانی decorator را انجام میدادید (بدون آرگومان، که به هر حال اشتباه است) و چون decorator() به undefined ارزیابی میشود (هیچ مقدار تعریفشدهای برنمیگرداند)، شما با undefined تزئین میکردید و undefined یک دکوراتور نیست.


اگر دکوراتور برای اجرا به اطلاعات بیشتری نیاز دارد، از نظر مفهومی دوست دارید که آرگومانهای بیشتری از سه آرگومان مورد نیاز بگیرد، اما این مجاز نیست. چیزی که **مجاز** است، این است که تابعی بسازید که اطلاعات اضافی را بگیرد و **یک دکوراتور برگرداند**. در اینجا تابعی که یک دکوراتور برمیگرداند آورده شده است:

const fnReturningDecorator = (name: string) => (
    target: any, key: PropertyKey, descriptor: PropertyDescriptor
) => {
    console.log("fnReturningDecorator", name, key);
}

و در اینجا نحوه استفاده از آن برای تزئین یک متد آورده شده است:

class Foo {    
    @fnReturningDecorator("hello") 
    method2() { } // "fnReturningDecorator",  "hello",  "method2" 
}

شما با عبارت fnReturningDecorator("hello") تزئین میکنید. توجه داشته باشید که باید fnReturningDecorator را با آرگومان string خود فراخوانی کنید و سپس با دکوراتور برگشتی تزئین کنید. اگر fnReturningDecorator را بدون آرگومان مینوشتید، شما با تابعی تزئین میکردید که یک آرگومان string میگیرد و یک تابع برمیگرداند، و این خودش دکوراتور نیست. دوباره، تابعی که یک دکوراتور بر میگرداند خودش دکوراتور نیست.

این معادل است با:

const hello = fnReturningDecorator("hello");

class Foo {
    @hello
    method2() { } // "fnReturningDecorator",  "hello",  "method2" 
}   

پس هیچ چیز خاصی درباره پرانتز وجود ندارد. پرانتزها فقط فراخوانی تابع هستند تا بتوانید یک دکوراتور دریافت کنید؛ آنها بخشی از نحو دکوراتور نیستند.


باز هم، هر عبارتی که به یک دکوراتور مناسب ارزیابی شود کار خواهد کرد. به عنوان یک مثال نهایی، در اینجا یک شیء با یک ویژگی که یک دکوراتور است آورده شده است:

const objHoldingDecorator = {
    decorator: (
        target: any, key: PropertyKey, descriptor: PropertyDescriptor
    ) => {
        console.log("objHoldingDecorator", key);
    }
}

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

class Foo {
    @objHoldingDecorator.decorator
    method3() { } // "objHoldingDecorator",  "method3" 
}

باز هم بدون پرانتز، اما این بار یک نقطه داریم. دکوراتور objHoldingDecorator.decorator است؛ اگر سعی میکردید فقط با objHoldingDecorator تزئین کنید، با یک شیء غیرقابل فراخوانی تزئین کردهاید و این دکوراتور نیست.


لینک زمین بازی به کد