برنامه نویسی

استفاده از دکوراتورهای مدرن در TypeScript

نوشته شده توسط الک برونل✏️

The State of Developer Ecosystem 2022 تایپ اسکریپت را به عنوان زبان برنامه نویسی سریعترین رشد معرفی کرد. سخت نیست که بفهمیم چرا. این ابرمجموعه محبوب جاوا اسکریپت چک کردن نوع، enums و سایر پیشرفت‌ها را فراهم می‌کند. اما اغلب، TypeScript ویژگی‌های مورد انتظار را معرفی می‌کند که هنوز بخشی از استاندارد ECMAScript که جاوا اسکریپت بر آن تکیه دارد، نیستند.

یک مثال، معرفی مجدد دکوراتورها در TypeScript 5.0 است که به زودی منتشر خواهد شد. decorators یک تکنیک متا برنامه نویسی است که در سایر زبان های برنامه نویسی نیز یافت می شود. اگر یک توسعه‌دهنده برنامه یا نویسنده کتابخانه هستید که علاقه‌مند به استفاده از تزئینات رسمی TypeScript جدید هستید، باید از نحو جدید استفاده کنید و تفاوت‌های بین مجموعه ویژگی‌های قدیمی و جدید را درک کنید. تفاوت های API گسترده است و بعید است که دکوراتورهای قدیمی با نحو جدید خارج از جعبه کار کنند.

در این مقاله، تاریخچه استفاده از دکوراتورها در TypeScript را مرور می‌کنیم، مزایای مرتبط با دکوراتورها را در TypeScript 5.0 مورد بحث قرار می‌دهیم، در یک نسخه آزمایشی با استفاده از دکوراتورهای مدرن قدم می‌زنیم، و چگونگی اصلاح دکوراتورهای موجود را بررسی می‌کنیم.

NB، همه APIها در TypeScript 5.0 تغییرات زیادی کرده اند. برای این مقاله، ما بر روی دکوراتورهای روش کلاس تمرکز خواهیم کرد.

پرش به جلو:

تاریخچه دکوراتورهای TypeScript

Decorators قابلیتی است که توسعه دهندگان را قادر می سازد تا با افزودن سریع عملکرد به کلاس ها، ویژگی های کلاس و متدهای کلاس، صفحه دیگ را کاهش دهند. زمانی که TypeScript برای اولین بار دکوراتورها را معرفی کرد، از مشخصات ECMAScript پیروی نمی کرد. این برای توسعه دهندگان عالی نبود، زیرا کدهای ارسال شده به طور ایده آل از هر کامپایلر جاوا اسکریپت باید با استانداردهای وب مطابقت داشته باشد!

استفاده از دکوراتورها نیاز به تنظیم یک --experimentalDecorators پرچم کامپایلر تجربی چندین کتابخانه محبوب TypeScript، مانند type-graphql و inversify، بر این پیاده سازی متکی هستند.

در اینجا مثالی از یک دکوراتور با روش کلاسی ساده آورده شده است که ارگونومی پیشرفته نحو جدید را نشان می دهد:

function debugMethod(_target: unknown, memberName: string, propertyDescriptor: PropertyDescriptor) {
  return {
    get() {
      const wrapperFunction = (...arguments_: unknown[]) => {
        const now = new Date(Date.now());
        console.log('start time', now.toISOString());
        propertyDescriptor.value.apply(this, arguments_);
        const end = new Date(Date.now());
        console.log('end time', end.toISOString());
      };
      Object.defineProperty(this, memberName, {
        value: wrapperFunction,
        configurable: true,
        writable: true,
      });
      return wrapperFunction;
    },
  };
}
class ComplexClass {
  @debugMethod
  public complexMethod(a: number): void {
    console.log("DOING COMPLEX STUFF!");
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در کد بالا می بینیم که debugMethod decorator ویژگی متد کلاس را با استفاده از آن لغو می کند Object.defineProperty، اما به طور کلی، دنبال کردن کد آسان نیست. همچنین، آرگومان‌ها از نظر نوع ایمن نیستند، که ایمنی ما را در داخل محدود می‌کند wrapperFunction. علاوه بر این، کامپایلر در صورت استفاده از این دکوراتور در مورد استفاده نامعتبر، مانند ویژگی کلاس، خراب نخواهد شد.

ما می‌توانیم از ژنریک‌های TypeScript برای دستیابی به ایمنی نوع استفاده کنیم، اما TypeScript انواع عمومی را استنباط نمی‌کند و این باعث می‌شود مصرف آن‌ها دردسرساز باشد. بنابراین، نوشتن دکوراتورهای پیچیده به دلیل مقادیر ناشناخته ای که کاربران می توانند در آنها وارد کنند، دشوار است.

نسخه مدرن دکوراتورها که به طور رسمی در TypeScript 5.0 عرضه خواهد شد، دیگر نیازی به پرچم کامپایلر ندارد و از پیشنهاد رسمی ECMAScript Stage-3 پیروی می کند. در کنار یک پیاده سازی پایدار که از استانداردهای ECMAScript پیروی می کند، دکوراتورها اکنون به طور یکپارچه با سیستم نوع TypeScript کار می کنند و عملکردهای پیشرفته تری را نسبت به نسخه اصلی امکان پذیر می کنند.

با پیاده سازی جدید دکوراتورها در TypeScript 5.0، این جنبه ها تا حد زیادی بهبود یافته است. بیا یک نگاهی بیندازیم.

دکوراتورها در TypeScript 5.0

TypeScript 5.0 ارگونومی بهتر، ایمنی نوع بهبود یافته و موارد دیگر را ارائه می دهد. در اینجا یک مثال مشابه از دکوراتور TypeScript 5.0 است که یک متد کلاس را لغو می کند:

function debugMethod(originalMethod: any, _context: any) {
  function replacementMethod(this: any, ...args: any[]) {
    const now = new Date(Date.now());
    console.log('start time', now.toISOString());
    const result = originalMethod.call(this, ...args);
    const end = new Date(Date.now());
    console.log('end time', end.toISOString());
    return result;
  }
  return replacementMethod;
}
class ComplexClass {
  @debugMethod
  complexMethod(a: number): void {
    console.log("DOING STUFF!");
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

NB، برای امتحان TypeScript در یک زمین بازی آنلاین، فقط نسخه را به “nightly” یا “5.0” تغییر دهید. با پیاده سازی جدید، به سادگی برگرداندن تابع می تواند جایگزین آن شود. نیازی به Object.defineProperty. این امر دکوراتورها را برای پیاده سازی و درک آسان تر می کند. در کنار این بهبود، بیایید آن را کاملاً ایمن کنیم:

function debugMethod<TThis, TArgs extends [string, number], TReturn extends number>(
  originalMethod: Function,
  context: ClassMethodDecoratorContext<TThis, (this: TThis, ...args: TArgs) => TReturn>
) {
  function replacementMethod(this: TThis, a: TArgs[0], b: TArgs[1]): TReturn {
    const now = new Date(Date.now());
    console.log('start time', now.toISOString());
    const result = originalMethod.call(this, a, b);
    const end = new Date(Date.now());
    console.log('end time', end.toISOString());
    return result;
  }
  return replacementMethod;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

عملکرد دکوراتورها در TypeScript 5.0 بسیار بهبود یافته است و اکنون موارد زیر را پشتیبانی می کند:

  • استفاده از ژنریک برای تایپ آرگومان های یک روش و برگرداندن یک مقدار. متد باید یک رشته و یک عدد را بپذیرد، TArgsو یک عدد را برگردانید، TReturn
  • تایپ کردن originalMethod به عنوان یک Function
  • با استفاده از ClassMethodDecoratorContext نوع کمکی داخلی؛ این برای همه انواع دکوراتور وجود دارد

ما می‌توانیم با بررسی خطاها در صورت استفاده نادرست، آزمایش کنیم که ببینیم آیا دکوراتور ما واقعاً از نظر نوع ایمن است یا خیر: اکنون، بیایید به یک مورد استفاده واقعی برای تزئینات جدید TypeScript 5.0 نگاه کنیم.

دمو کارخانه دکوراتور

ما می‌توانیم از ایمنی نوع موجود در دکوراتورهای TypeScript 5.0 برای ایجاد عملکردهایی استفاده کنیم که دکوراتور را برمی‌گردانند، که در غیر این صورت به عنوان کارخانه دکوراتور شناخته می‌شود. کارخانه های دکوراتور به ما این امکان را می دهند که با عبور دادن برخی از پارامترها در کارخانه، رفتار دکوراتورهای خود را سفارشی کنیم.

برای نسخه ی نمایشی خود، یک کارخانه تزئینی ایجاد می کنیم که آرگومان متد کلاس را بر اساس آرگومان های خود تغییر می دهد. این کار با عملگر سه تایی نوع TypeScript امکان پذیر است. مثال ما از چارچوب های REST API مانند NestJS الهام گرفته شده است.

ما با ماژول خود تماس می گیریم rest-framework. بیایید با ایجاد یک پروژه TypeScript خالی شروع کنیم ts-node:

$ mkdir rest-framework
$ cd rest-framework
$ npm init -y
$ npm install -D typescript@5.0.4 @types/node ts-node
$ touch index.ts
$ echo "console.log('Hello, world!');" > index.ts
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در مرحله بعد، اسکریپت را برای ساخت و اجرای پروژه تعریف می کنیم package.json:

{
  // ...
  "scripts": {
    "build": "tsc",
    "start": "ts-node index.ts"
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بریم بدویم npm start برای دیدن آن در عمل:

$ npm start
Hello, world!
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

حالا بیایید انواع خود را تعریف کنیم:

interface RouteOptionsAuthEnabled {
  auth: true;
}
interface RouteOptionsAuthDisabled {
  auth: false;
}
type RouteArguments = [string] | [];
type RouteDecorator<TThis, TArgs extends RouteArguments> = (
  originalMethod: Function,
  context: ClassMethodDecoratorContext<
    TThis,
    (this: TThis, ...args: TArgs) => string
  >
) => void;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بعد، اجازه دهید دکوراتور کارخانه را تعریف کنیم:

function Route<
  TThis,
  // The user can enable or disable auth
  TOptions extends RouteOptionsAuthEnabled | RouteOptionsAuthDisabled
>(
  options: TOptions
): RouteDecorator<
  TThis,
  // Do not accept a function that uses a string for an argument if auth is disabled
  TOptions extends RouteOptionsAuthEnabled ? [string] : []
> {
  return <TThis>(
    target: (
      this: TThis,
      ...args: TOptions extends RouteOptionsAuthEnabled ? [string] : []
    ) => string,
    context: ClassMethodDecoratorContext<
      TThis,
      (
        this: TThis,
        ...args: TOptions extends RouteOptionsAuthEnabled ? [string] : []
      ) => string
    >
  ) => {};
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون یک طراح مسیر داریم که بسته به گزینه های کاربر، انواع پارامتر متد کلاس را تغییر می دهد.

بیایید یک مثال ایجاد کنیم Route کلاس به عنوان مورد آزمایشی ما عمل می کند:

class Controller {
  @Route({ auth: true })
  get(authHeaderValue: string): string {
    console.log("get http method handled!");
    return "response";
  }
  @Route({ auth: false })
  post(): string {
    console.log("post http method handled!");
    return "response";
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر بخواهیم از آن استفاده کنیم، می بینیم که TypeScript در کامپایل شکست خورده است authHeaderValue در post مسیر: AuthHeaderValue ارسال مسیر تایپ اسکریپت شکست مورد استفاده کارخانه دکوراتور یک مثال ساده است، اما قدرت کاری که دکوراتورهای ایمن می توانند انجام دهند را نشان می دهد.

بازسازی دکوراتورهای موجود

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

برای بهترین نتیجه، مراحل زیر را برای بازسازی دکوراتورهای موجود دنبال کنید:

  • تست های واحد را برای دکوراتورهای خود بنویسید
  • حذف یا جعل experimentalDecorators پرچم های کامپایلر TypeScript
  • این خلاصه گسترده از نحوه عملکرد پیشنهاد جدید را بخوانید
  • محدودیت های دکوراتورهای مدرن را درک کنید (در ادامه این مقاله به این موضوع خواهیم پرداخت)
  • دکوراتورها را بدون استفاده از نوع و استفاده بازنویسی کنید any به جای همه انواع
  • مطمئن شوید که آزمون های واحد قبول می شوند
  • انواع را اضافه کنید

آشنایی با محدودیت‌های دکوراتورهای مدرن

اجرای مدرن دکوراتور خبر خوبی برای توسعه دهندگان TypeScript است، اما ویژگی های قابل توجهی وجود ندارد. اول، هیچ پشتیبانی از پارامترهای روش تزئین وجود ندارد. این در مشخصات پیشنهاد است، بنابراین امیدواریم در مشخصات نهایی گنجانده شود. حذف آن قابل توجه است زیرا کتابخانه های محبوب مانند type-graphql، از این در راه های مهمی مانند حل کننده های نوشتن استفاده کنید:

@Query(returns => Recipe)
async recipe(@Arg("recipeId") recipeId: string) {
  return this.recipeRepository.findOneById(recipeId);
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

دوم، TypeScript 5.0 نمی تواند متادیتای دکوراتور را منتشر کند. پس از آن، با Reflect API یکپارچه نمی شود و با بسته npm reflect-metadata کار نمی کند.

سوم، --emitDecoratorMetadata flag که قبلاً برای دسترسی و اصلاح ابرداده‌ها برای دکوراتورهای معین استفاده می‌شد، دیگر پشتیبانی نمی‌شود. متأسفانه، هیچ راهی واقعی برای دستیابی به عملکرد مشابه با دریافت ابرداده در زمان اجرا وجود ندارد. مواردی وجود دارد که می توان آنها را بازسازی کرد. به عنوان مثال، اجازه دهید یک دکوراتور تعریف کنیم که انواع پارامترهای یک تابع را در زمان اجرا تأیید می کند:

function validateParameterType(target: any, propertyKey: string | symbol): void {
  const methodParameterTypes: (string | unknown)[] =
    Reflect.getMetadata("design:paramtypes", target, propertyKey) ?? [];
  const firstParameterType = methodParameterTypes[0];
  if (typeof firstParameterType !== "string") {
    throw new TypeError("First parameter must be a string");
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما می توانیم عملکرد مشابهی را با ایمنی نوع بهبود یافته ارائه شده توسط TypeScript 5.0 به دست آوریم. ما به سادگی آرگومان های روشی را که تزئین می کنیم اضافه می کنیم، مانند:

function debugMethod<TThis, TArgs extends [string], TReturn>(
) {
...
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در تئوری، ما می‌توانیم از این رویکرد برای دکوراتورهایی که به دریافت انواع از Reflect وابسته هستند، استفاده کنیم design:type، design:paramtypes، و design:returntype. این یک روش متفاوت برای نوشتن دکوراتورها است. این یک refactor ساده نیست، زیرا نیاز به استفاده از نوع استنتاج TypeScript برای احیا کردن نحوه کسب و اعتبارسنجی انواع دارد.

نتیجه

اجرای جدید دکوراتور در TypeScript 5.0 از پیشنهاد رسمی ECMAScript Stage-3 پیروی می کند و اکنون از نظر نوع ایمن است و پیاده سازی و درک آن را آسان تر می کند. با این حال، برخی از ویژگی های قابل توجه مانند پشتیبانی از پارامترهای روش تزئین و توانایی انتشار ابرداده های تزئینی وجود ندارد.

دکوراتورهای پایه را می توان به راحتی به نسخه TypeScript 5.0 تغییر داد، اما موارد استفاده پیشرفته به تلاش بیشتری نیاز دارد. توسعه دهندگان می توانند دکوراتورهای موجود را اصلاح کنند تا از API جدید استفاده کنند و از مزایای مرتبط بهره ببرند. آن‌ها می‌توانند کمتر به کتابخانه‌های خارجی وابسته باشند و کمتر احتمال دارد که کد را در آینده اصلاح کنند. این تغییرات در اجرای دکوراتورهای TypeScript یک مزیت برای اکوسیستم گسترده تر است، اما پذیرش جامعه ممکن است مدتی طول بکشد.


LogRocket: قابلیت مشاهده کامل در وب و برنامه های تلفن همراه شما

ثبت نام LogRocket

LogRocket یک راه حل مانیتورینگ اپلیکیشن frontend است که به شما امکان می دهد مشکلات را به گونه ای تکرار کنید که گویی در مرورگر شما اتفاق افتاده اند. LogRocket به جای حدس زدن چرایی خطاها، یا درخواست از کاربران برای اسکرین شات ها و گزارش ها، به شما امکان می دهد جلسه را دوباره پخش کنید تا به سرعت متوجه شوید که چه اشتباهی رخ داده است. بدون در نظر گرفتن چارچوب، با هر برنامه ای کاملاً کار می کند و دارای پلاگین هایی برای ثبت زمینه اضافی از Redux، Vuex، و @ngrx/store است.

LogRocket علاوه بر ثبت اقدامات و وضعیت Redux، گزارش‌های کنسول، خطاهای جاوا اسکریپت، stacktraces، درخواست‌ها/پاسخ‌های شبکه با هدر + بدنه، ابرداده مرورگر و گزارش‌های سفارشی را ثبت می‌کند. همچنین DOM را برای ضبط HTML و CSS در صفحه ابزار می کند و ویدیوهای پیکسلی کاملی را حتی از پیچیده ترین برنامه های تک صفحه ای و تلفن همراه بازسازی می کند.

آن را به صورت رایگان امتحان کنید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

همچنین ببینید
بستن
دکمه بازگشت به بالا