Enums و Const Enums در تایپ اسکریپت: راهنمای کاربردها و تفاوت‌ها

فهرست مطالب

در دنیای توسعه نرم‌افزار مدرن، به خصوص در حوزه توسعه وب با استفاده از فریم‌ورک‌هایی مانند Angular، React و Vue، استفاده از TypeScript به عنوان یک زبان برنامه‌نویسی قدرتمند و Type-Safe به امری ضروری تبدیل شده است. TypeScript با افزودن قابلیت‌های تایپ‌بندی استاتیک به جاوا اسکریپت، به توسعه‌دهندگان کمک می‌کند تا کدهای قابل نگهداری‌تر، مقیاس‌پذیرتر و با خطایابی کمتر بنویسند. یکی از ویژگی‌های کلیدی که TypeScript برای سازماندهی و مدیریت مجموعه‌ای از مقادیر ثابت و مرتبط ارائه می‌دهد، مفهوم Enums (مخفف Enumerations) است.

Enums ابزاری قدرتمند برای تعریف مجموعه‌ای از ثابت‌های نام‌گذاری شده هستند. این ثابت‌ها، که به آن‌ها اعضا گفته می‌شود، یک مقدار مشخص (معمولاً عددی یا رشته‌ای) را نشان می‌دهند. استفاده از Enums به جای استفاده مستقیم از اعداد “جادویی” یا رشته‌های تکراری، به بهبود خوانایی کد، کاهش خطاهای تایپی و تسهیل نگهداری و گسترش نرم‌افزار کمک شایانی می‌کند. در حقیقت، با استفاده از Enums، شما می‌توانید مفاهیم انتزاعی را در کد خود به شکلی معنادار و قابل فهم نمایش دهید.

با این حال، TypeScript فراتر از Enums معمولی رفته و یک نوع خاص‌تر به نام Const Enums را نیز معرفی کرده است. Const Enums، در نگاه اول، بسیار شبیه به Enums معمولی به نظر می‌رسند، اما تفاوت‌های ظریف و در عین حال بسیار مهمی در نحوه کامپایل شدن و رفتار در زمان اجرا دارند. این تفاوت‌ها می‌توانند تأثیر قابل توجهی بر اندازه نهایی باندل (Bundle Size)، عملکرد برنامه و حتی نحوه دسترسی به مقادیر در زمان اجرا داشته باشند.

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

مقدمه‌ای بر Enums در تایپ اسکریپت: چرا و چگونه؟

Enums (Enumerations) در TypeScript راهی برای تعریف مجموعه‌ای از ثابت‌های نام‌گذاری شده هستند. این ساختار داده‌ای به شما امکان می‌دهد تا مجموعه‌ای از مقادیر مرتبط را در قالب یک نوع واحد گروه‌بندی کنید. به جای استفاده از اعداد یا رشته‌های خام که می‌توانند مستعد خطا و دشوار برای خواندن باشند، Enums یک لایه انتزاعی و معنایی به کد شما اضافه می‌کنند.

چرا استفاده از Enums اهمیت دارد؟

تصور کنید در یک برنامه نیاز به نمایش وضعیت‌های مختلف یک شیء (مانند یک سفارش، یک کار یا یک حالت UI) دارید. بدون Enums، ممکن است از اعداد جادویی یا رشته‌های تکراری استفاده کنید:


// رویکرد بدون Enum: استفاده از اعداد جادویی
const PENDING_ORDER = 0;
const PROCESSING_ORDER = 1;
const COMPLETED_ORDER = 2;

function getOrderStatusText(status: number): string {
    if (status === PENDING_ORDER) return "سفارش در حال انتظار";
    if (status === PROCESSING_ORDER) return "سفارش در حال پردازش";
    if (status === COMPLETED_ORDER) return "سفارش تکمیل شده";
    return "وضعیت نامشخص";
}

console.log(getOrderStatusText(0)); // خروجی: سفارش در حال انتظار
console.log(getOrderStatusText(1)); // خروجی: سفارش در حال پردازش
console.log(getOrderStatusText(3)); // خروجی: وضعیت نامشخص (خطا در زمان اجرا)

این رویکرد چندین مشکل اساسی دارد:

  • خوانایی پایین: کد 0، 1، 2 به خودی خود معنای خاصی ندارند. هر کسی که کد را می‌خواند، باید بداند که هر عدد به چه معناست. این امر نیاز به مراجعه مداوم به مستندات یا تعریف ثابت‌ها دارد.
  • مستعد خطا: به راحتی می‌توان به جای 0، اشتباهاً 3 را وارد کرد که معنای خاصی ندارد و منجر به خطای منطقی در زمان اجرا می‌شود که تشخیص آن دشوار است. TypeScript در اینجا هیچ کمکی نمی‌کند.
  • نگهداری دشوار: اگر بخواهید وضعیت جدیدی اضافه کنید یا ترتیب را تغییر دهید، باید تمام مکان‌هایی که از آن اعداد استفاده شده‌اند را پیدا کرده و به‌روزرسانی کنید. این فرآیند پرخطر و زمان‌بر است.
  • عدم Type Safety: در جاوا اسکریپت و حتی با type number در TypeScript، هیچ محدودیتی برای مقادیری که می‌توانند به تابع getOrderStatusText پاس داده شوند، وجود ندارد.

Enums این مشکلات را با افزودن یک لایه معنایی و تایپ‌بندی قوی حل می‌کنند:


enum OrderStatus {
    Pending,
    Processing,
    Completed,
    Cancelled
}

function displayOrderStatus(status: OrderStatus) {
    switch (status) {
        case OrderStatus.Pending:
            console.log("سفارش در حال انتظار برای پردازش است.");
            break;
        case OrderStatus.Processing:
            console.log("سفارش در حال پردازش است.");
            break;
        case OrderStatus.Completed:
            console.log("سفارش تکمیل شده است.");
            break;
        case OrderStatus.Cancelled:
            console.log("سفارش لغو شده است.");
            break;
        default:
            // TypeScript به ما هشدار می دهد اگر تمام موارد را پوشش ندهیم (با 'strictNullChecks' و 'noImplicitReturns' و 'noFallthroughCasesInSwitch')
            const exhaustiveCheck: never = status;
            console.error(`وضعیت نامشخص: ${exhaustiveCheck}`);
    }
}

displayOrderStatus(OrderStatus.Processing); // خروجی: سفارش در حال پردازش است.
displayOrderStatus(OrderStatus.Completed);  // خروجی: سفارش تکمیل شده است.
// displayOrderStatus(99); // خطا در زمان کامپایل: Argument of type '99' is not assignable to parameter of type 'OrderStatus'.

در این مثال، OrderStatus یک Enum است که چهار وضعیت ممکن را برای یک سفارش تعریف می‌کند. این کد بسیار خواناتر، کمتر مستعد خطا و از نظر Type Safety بسیار قوی‌تر است. TypeScript اطمینان می‌دهد که فقط مقادیر معتبر OrderStatus می‌توانند به displayOrderStatus ارسال شوند.

انواع Enum در تایپ اسکریپت: از عدد تا رشته و ترکیبی

TypeScript سه نوع اصلی Enum را پشتیبانی می‌کند که هر یک ویژگی‌ها و کاربردهای خاص خود را دارند.

۱. Numeric Enums (انام‌های عددی)

Numeric Enums رایج‌ترین نوع Enum هستند. به صورت پیش‌فرض، اگر مقداری برای اعضای Enum تعیین نکنید، TypeScript به آن‌ها مقادیر عددی اختصاص می‌دهد که از 0 شروع شده و به ترتیب افزایش می‌یابند. این رفتار مشابه Enums در زبان‌هایی مانند C# یا Java است.


enum Direction {
    Up,    // 0
    Down,  // 1
    Left,  // 2
    Right  // 3
}

let userDirection: Direction = Direction.Up;
console.log(userDirection);      // خروجی: 0
console.log(Direction.Down);     // خروجی: 1

می‌توانید مقدار اولیه را به صورت دستی تنظیم کنید؛ در این صورت، بقیه اعضا به صورت افزایشی ادامه خواهند یافت:


enum HttpCode {
    OK = 200,
    Created = 201,
    BadRequest = 400,
    Unauthorized, // این مقدار 401 خواهد بود (400 + 1)
    Forbidden = 403,
    NotFound = 404,
    InternalServerError // این مقدار 405 خواهد بود (404 + 1)
}

console.log(HttpCode.OK);                  // خروجی: 200
console.log(HttpCode.Unauthorized);        // خروجی: 401
console.log(HttpCode.InternalServerError); // خروجی: 405

Reverse Mapping (نقشه معکوس): یکی از ویژگی‌های منحصر به فرد و قدرتمند Numeric Enums، قابلیت Reverse Mapping است. یعنی علاوه بر اینکه می‌توانید از نام عضو به مقدار عددی دسترسی پیدا کنید (مثلاً Direction.Up که 0 است)، می‌توانید از مقدار عددی نیز به نام عضو دسترسی پیدا کنید (مثلاً Direction[0] که "Up" است). TypeScript برای پشتیبانی از این ویژگی، کدی را در خروجی جاوا اسکریپت تولید می‌کند که یک شیء دوطرفه ایجاد می‌کند.


enum Fruit {
    Apple,   // 0
    Banana,  // 1
    Orange   // 2
}

let myFruit: Fruit = Fruit.Banana;
console.log(myFruit);           // خروجی: 1
console.log(Fruit[myFruit]);    // خروجی: Banana (Reverse Mapping)
console.log(Fruit[0]);          // خروجی: Apple
console.log(Fruit[2]);          // خروجی: Orange

خروجی جاوا اسکریپت تولید شده برای Fruit Enum به شکل زیر خواهد بود:


var Fruit;
(function (Fruit) {
    Fruit[Fruit["Apple"] = 0] = "Apple";
    Fruit[Fruit["Banana"] = 1] = "Banana";
    Fruit[Fruit["Orange"] = 2] = "Orange";
})(Fruit || (Fruit = {}));

همانطور که مشاهده می‌کنید، کامپایلر TypeScript کدی را تولید می‌کند که یک شیء Fruit در زمان اجرا ایجاد می‌کند. این شیء هم شامل کلیدهای نام (مثلاً Fruit.Apple) که به مقادیر عددی نگاشت می‌شوند، و هم شامل کلیدهای عددی (مثلاً Fruit[0]) که به نام‌های رشته‌ای نگاشت می‌شوند، است. این ساختار امکان دسترسی دوطرفه را فراهم می‌کند.

۲. String Enums (انام‌های رشته‌ای)

String Enums به اعضای خود مقادیر رشته‌ای اختصاص می‌دهند. این نوع Enums در TypeScript 2.4 معرفی شد و مزایای قابل توجهی نسبت به Numeric Enums دارد، به خصوص در زمینه خوانایی و دیباگ‌پذیری.


enum UserAction {
    Click = "CLICK_EVENT",
    Hover = "HOVER_EVENT",
    Scroll = "SCROLL_EVENT_DOWN",
    Submit = "FORM_SUBMISSION"
}

let action: UserAction = UserAction.Click;
console.log(action); // خروجی: CLICK_EVENT

function logUserAction(actionType: UserAction) {
    console.log(`کاربر عملیات: "${actionType}" را در تاریخ ${new Date().toLocaleTimeString()} انجام داد.`);
}

logUserAction(UserAction.Submit); // خروجی: کاربر عملیات: "FORM_SUBMISSION" را در تاریخ ... انجام داد.

مزایای String Enums:

  • خوانایی بهتر: مقادیر رشته‌ای معمولاً معنادارتر و قابل فهم‌تر از اعداد هستند، به خصوص هنگام دیباگ کردن، نمایش مقادیر در لاگ‌ها، یا کار با APIهایی که مقادیر رشته‌ای مشخصی را انتظار دارند.
  • دیباگ‌پذیری بالاتر: وقتی یک مقدار Enum را در کنسول لاگ می‌کنید، مستقیماً رشته‌ای را مشاهده می‌کنید که بیانگر معنای آن است، نه یک عدد که نیاز به lookup دستی دارد.
  • عدم وجود Reverse Mapping: برخلاف Numeric Enums، String Enums قابلیت Reverse Mapping ندارند. این بدان معنی است که کد جاوا اسکریپت کمتری تولید می‌شود و اندازه باندل نهایی کوچکتر خواهد بود. این ویژگی می‌تواند در سناریوهای خاصی یک مزیت به حساب آید زیرا سربار کمتری در زمان اجرا ایجاد می‌کند.
  • امنیت ضمنی: مقادیر رشته‌ای که مستقیماً در کد ظاهر می‌شوند (مثلاً در یک درخواست HTTP)، اغلب به طور طبیعی امنیت بیشتری دارند تا مقادیر عددی که نیاز به یک رمزگشایی یا نگاشت دارند.

خروجی جاوا اسکریپت برای UserAction Enum به شکل زیر خواهد بود:


var UserAction;
(function (UserAction) {
    UserAction["Click"] = "CLICK_EVENT";
    UserAction["Hover"] = "HOVER_EVENT";
    UserAction["Scroll"] = "SCROLL_EVENT_DOWN";
    UserAction["Submit"] = "FORM_SUBMISSION";
})(UserAction || (UserAction = {}));

همانطور که می‌بینید، تنها یک شیء با کلیدهای رشته‌ای و مقادیر رشته‌ای تولید می‌شود، بدون هیچ گونه کدی برای نگاشت معکوس. این بهینه‌تر از Numeric Enums است.

۳. Heterogeneous Enums (انام‌های ناهمگون)

TypeScript به شما اجازه می‌دهد تا Enums با ترکیبی از اعضای عددی و رشته‌ای ایجاد کنید. این نوع Enums کمتر رایج است و معمولاً توصیه نمی‌شود زیرا می‌تواند به خوانایی و قابلیت نگهداری کد آسیب برساند.


enum DataStatus {
    Loaded = 200,
    Fetching = "FETCHING",
    Error = 500,
    Empty = "NO_DATA"
}

console.log(DataStatus.Loaded);   // خروجی: 200
console.log(DataStatus.Fetching); // خروجی: FETCHING
console.log(DataStatus.Error);    // خروجی: 500
console.log(DataStatus.Empty);    // خروجی: NO_DATA

// Reverse mapping فقط برای اعضای عددی کار می کند
console.log(DataStatus[200]);       // خروجی: Loaded
console.log(DataStatus["FETCHING"]); // خروجی: undefined (چون نگاشت معکوس برای رشته‌ای وجود ندارد)

با توجه به پیچیدگی و پتانسیل برای سردرگمی، استفاده از Heterogeneous Enums به ندرت توصیه می‌شود. بهتر است برای مقادیر عددی و رشته‌ای، Enums جداگانه تعریف کنید یا از الگوهای جایگزین استفاده نمایید تا شفافیت کد حفظ شود.

Const Enums: مفهوم، مزایا و محدودیت‌های بهینه‌سازی

همانطور که دیدیم، Enums معمولی (غیر const) در زمان کامپایل به اشیاء جاوا اسکریپت تبدیل می‌شوند که در زمان اجرا (Runtime) در دسترس هستند. این رفتار در بسیاری از موارد مفید است، به خصوص زمانی که نیاز به دسترسی دینامیک به مقادیر Enum یا انجام Reverse Mapping دارید. اما این کار هزینه دارد: افزایش اندازه باندل نهایی و سربار جزئی در زمان اجرا به دلیل ایجاد و نگهداری این اشیاء.

اینجاست که Const Enums وارد می‌شوند. Const Enums با افزودن کلمه کلیدی const قبل از تعریف enum ایجاد می‌شوند و رویکرد متفاوتی برای کامپایل دارند:


const enum LogLevel {
    Error,  // 0
    Warn,   // 1
    Info,   // 2
    Debug   // 3
}

let currentLevel: LogLevel = LogLevel.Info;
console.log(currentLevel); // این خط در خروجی JS به console.log(2); تبدیل می شود.

تفاوت اصلی: حذف Enum در زمان کامپایل (Inlining)

تفاوت اساسی و مهم‌ترین ویژگی Const Enums این است که آن‌ها در زمان کامپایل به طور کامل از کد جاوا اسکریپت حذف می‌شوند. به جای اینکه یک شیء جاوا اسکریپت برای Enum در زمان اجرا ایجاد شود، کامپایلر TypeScript مستقیماً مقدار عددی (یا رشته‌ای) هر عضو Const Enum را در هر مکانی که استفاده شده است، جایگزین (inline) می‌کند. این فرآیند به عنوان “inlining” شناخته می‌شود.

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


const enum LogLevel {
    Error, // 0
    Warn,  // 1
    Info,  // 2
    Debug  // 3
}

function logMessage(message: string, level: LogLevel) {
    switch (level) {
        case LogLevel.Error:
            console.error(`ERROR: ${message}`);
            break;
        case LogLevel.Warn:
            console.warn(`WARN: ${message}`);
            break;
        case LogLevel.Info:
            console.info(`INFO: ${message}`);
            break;
        case LogLevel.Debug:
            console.debug(`DEBUG: ${message}`);
            break;
    }
}

logMessage("خطا در سیستم", LogLevel.Error);
logMessage("اطلاعات کاربر", LogLevel.Info);

خروجی جاوا اسکریپت کامپایل شده (با فرض target: ES5 یا ES2015):


function logMessage(message, level) {
    switch (level) {
        case 0 /* LogLevel.Error */: // Inlined value 0
            console.error("ERROR: " + message);
            break;
        case 1 /* LogLevel.Warn */: // Inlined value 1
            console.warn("WARN: " + message);
            break;
        case 2 /* LogLevel.Info */: // Inlined value 2
            console.info("INFO: " + message);
            break;
        case 3 /* LogLevel.Debug */: // Inlined value 3
            console.debug("DEBUG: " + message);
            break;
    }
}
logMessage("خطا در سیستم", 0 /* LogLevel.Error */); // Inlined value 0
logMessage("اطلاعات کاربر", 2 /* LogLevel.Info */);  // Inlined value 2

همانطور که مشاهده می‌کنید، هیچ شیء LogLevel در خروجی جاوا اسکریپت وجود ندارد. به جای آن، هر جا که LogLevel.Error یا LogLevel.Info استفاده شده بود، مستقیماً با مقدار عددی مربوطه (0 یا 2) جایگزین شده است. کامنت‌ها (/* LogLevel.Error */) فقط برای کمک به خوانایی توسط انسان هستند و در کد نهایی واقعی (مینیمایز شده) حذف می‌شوند. این ویژگی اصلی Const Enums است که به آن‌ها اجازه می‌دهد به حداکثر بهینه‌سازی دست یابند.

مزایای Const Enums

  • کاهش چشمگیر اندازه باندل (Bundle Size): از آنجایی که هیچ کد جاوا اسکریپت اضافی برای تعریف Enum در زمان اجرا تولید نمی‌شود، اندازه فایل نهایی جاوا اسکریپت به طور قابل توجهی کوچک‌تر می‌شود. این امر به ویژه در برنامه‌های بزرگ، Single Page Applications (SPAs) و وب‌سایت‌هایی که عملکرد و سرعت بارگذاری برایشان حیاتی است، اهمیت دارد.
  • بهبود عملکرد در زمان اجرا: با حذف شیء Enum، هیچ سربار حافظه (memory overhead) یا زمان پردازشی (processing time) برای ایجاد یا دسترسی به مقادیر Enum در زمان اجرا وجود ندارد. دسترسی به مقادیر ثابت عددی یا رشته‌ای به طور مستقیم، سریع‌ترین حالت ممکن است زیرا صرفاً مقایسه‌های مستقیم انجام می‌شود.
  • Tree Shaking بهتر: ابزارهای Tree Shaking (مانند Webpack یا Rollup) می‌توانند کدهای غیرضروری (dead code) را از باندل نهایی حذف کنند. از آنجایی که Const Enums مستقیماً در کد جایگذاری می‌شوند، اگر یک عضو Const Enum در هیچ کجای برنامه استفاده نشود، آن مقدار به هیچ وجه در باندل نهایی ظاهر نخواهد شد. این به بهینه‌سازی بیشتر و حذف موثر کد مرده کمک می‌کند و منجر به یک بسته نرم‌افزاری نهایی سبک‌تر می‌شود.
  • پنهان‌سازی نام‌ها: نام‌های Enum (مانند LogLevel.Error) در خروجی جاوا اسکریپت نهایی ظاهر نمی‌شوند، که می‌تواند در سناریوهایی که نمی‌خواهید جزئیات داخلی Enumها در سمت کلاینت فاش شود، مفید باشد (اگرچه برای امنیت واقعی باید به روش‌های سمت سرور اعتماد کرد).

محدودیت‌های Const Enums

البته، مزایای Const Enums بدون محدودیت نیستند. این محدودیت‌ها مستقیماً از نحوه عملکرد آن‌ها در زمان کامپایل نشأت می‌گیرند:

  • عدم دسترسی در زمان اجرا: از آنجایی که Const Enums در زمان اجرا وجود ندارند و فقط در زمان کامپایل به مقادیر لیترال تبدیل می‌شوند، نمی‌توانید به آن‌ها به صورت دینامیک دسترسی پیدا کنید. به عنوان مثال، نمی‌توانید حلقه‌ای بر روی اعضای یک Const Enum بزنید یا از Object.keys()، Object.values() یا Object.entries() برای دریافت اعضای آن استفاده کنید.
  • فقط مقادیر ثابت (Literal Values): تمام اعضای یک Const Enum باید دارای مقادیر ثابت (literal values) باشند که در زمان کامپایل قابل تعیین باشند. نمی‌توانید از مقادیر محاسبه شده (computed values) که در زمان اجرا مشخص می‌شوند، استفاده کنید.
  • 
        const enum Color {
            Red = 1,
            // Green = Red * 2, // خطا: A 'const' enum member can only have a literal value.
            // Blue = Math.random() > 0.5 ? 3 : 4, // خطا: A 'const' enum member can only have a literal value.
            Yellow = "YELLOW" // رشته ثابت مجاز است
        }
        
  • عدم وجود Reverse Mapping: Const Enums از Reverse Mapping پشتیبانی نمی‌کنند، حتی اگر Numeric باشند. این به دلیل این است که شیء Enum در زمان اجرا وجود ندارد تا این نگاشت را فراهم کند. تلاش برای انجام این کار منجر به خطای کامپایل می‌شود.
  • 
        const enum StatusId {
            Active = 1,
            Inactive = 2
        }
    
        // console.log(StatusId[1]); // خطا: A 'const' enum member cannot be accessed using a string literal.
        //                       یا: Property '1' does not exist on type 'typeof StatusId'.
        // این خطا به وضوح نشان می‌دهد که در زمان اجرا شیء برای دسترسی وجود ندارد.
        
  • محدودیت در Export و Import با پرچم --preserveConstEnums: TypeScript یک پرچم کامپایلر به نام --preserveConstEnums دارد که در صورت فعال بودن، باعث می‌شود Const Enums حتی پس از inlining نیز در خروجی جاوا اسکریپت به عنوان یک شیء Enum معمولی (runtime object) حفظ شوند. با این حال، استفاده از این پرچم به دلیل تداخل با هدف اصلی Const Enums (حذف کامل از خروجی و بهینه‌سازی) توصیه نمی‌شود و معمولاً به معنای انتخاب نادرست نوع Enum است. اگر نیاز به حفظ Enum در زمان اجرا دارید، بهتر است از یک Enum معمولی استفاده کنید.

مقایسه جامع: Enum معمولی در مقابل Const Enum

برای درک بهتر زمان و مکان استفاده از هر یک از این Enums، بسیار مهم است که تفاوت‌های اصلی آن‌ها را به صورت عمیق درک کنیم. این تفاوت‌ها عمدتاً در نحوه کامپایل، رفتار در زمان اجرا و میزان انعطاف‌پذیری آن‌ها نهفته است.

۱. تولید کد جاوا اسکریپت (Code Generation)

  • Enum معمولی:

    یک شیء جاوا اسکریپت واقعی در زمان اجرا تولید می‌کند که شامل کلیدها و مقادیر Enum است. برای Numeric Enums، این شیء شامل نگاشت معکوس نیز می‌شود.

    
            enum Color { Red, Green, Blue }
            

    خروجی JS:

    
            var Color;
            (function (Color) {
                Color[Color["Red"] = 0] = "Red";
                Color[Color["Green"] = 1] = "Green";
                Color[Color["Blue"] = 2] = "Blue";
            })(Color || (Color = {}));
            
  • Const Enum:

    هیچ شیء جاوا اسکریپت در زمان اجرا تولید نمی‌کند. مقادیر Enum مستقیماً در هر مکانی که استفاده شده‌اند، جایگزین (inline) می‌شوند.

    
            const enum Size { Small, Medium, Large }
            console.log(Size.Medium);
            

    خروجی JS:

    
            console.log(1 /* Size.Medium */); // مقدار 1 به صورت مستقیم جایگذاری شده است.
            

۲. دسترسی در زمان اجرا (Runtime Access)

  • Enum معمولی:

    کاملاً در زمان اجرا در دسترس هستند. می‌توانید مقادیر آن‌ها را حلقه بزنید، آن‌ها را به عنوان آرگومان به توابع پاس دهید و به صورت دینامیک به آن‌ها دسترسی پیدا کنید (مثلاً با استفاده از نام یک رشته برای دریافت مقدار آن).

    
            enum Permissions { Read, Write, Execute }
    
            function checkPermission(permission: Permissions) {
                console.log(`Checking permission: ${Permissions[permission]} (${permission})`);
            }
    
            checkPermission(Permissions.Read); // خروجی: Checking permission: Read (0)
    
            // حلقه زدن بر روی اعضا - این تنها برای Numeric Enums کارآمد است
            for (let key in Permissions) {
                if (isNaN(Number(key))) { // برای فیلتر کردن مقادیر عددی Reverse Mapping
                    console.log(`Permission Name: ${key}, Value: ${Permissions[key as keyof typeof Permissions]}`);
                }
            }
            

    خروجی بخشی از حلقه (نام‌های رشته‌ای Enum و مقادیر آن‌ها):

    
            Permission Name: Read, Value: 0
            Permission Name: Write, Value: 1
            Permission Name: Execute, Value: 2
            
  • Const Enum:

    در زمان اجرا وجود ندارند. تلاش برای دسترسی به آن‌ها به صورت دینامیک (مثلاً Size[0] یا حلقه زدن) منجر به خطای کامپایل یا رفتار غیرمنتظره در زمان اجرا می‌شود زیرا هیچ شیئی برای دسترسی وجود ندارد.

    
            const enum HttpMethod { Get, Post, Put, Delete }
    
            // console.log(HttpMethod[0]); // خطا در زمان کامپایل
            // console.log(Object.keys(HttpMethod)); // خطا در زمان کامپایل: 'HttpMethod' only refers to a type, but is being used as a value here.
            

۳. بهینه‌سازی (Optimization) و اندازه باندل (Bundle Size)

  • Enum معمولی:

    به دلیل تولید کد جاوا اسکریپت در زمان اجرا، اندازه باندل را افزایش می‌دهند. این افزایش بسته به تعداد و پیچیدگی Enumها می‌تواند جزئی یا قابل توجه باشد. این Enums همچنین نیاز به پردازش در زمان بارگذاری برنامه دارند.

  • Const Enum:

    به دلیل inlining کامل مقادیر و عدم تولید هیچ شیء جاوا اسکریپتی، به کاهش چشمگیر اندازه باندل کمک می‌کنند. این بهینه‌سازی عملکرد بارگذاری برنامه را بهبود می‌بخشد و Tree Shaking را کارآمدتر می‌کند.

۴. پشتیبانی از Computed Members

  • Enum معمولی:

    می‌توانند شامل اعضای محاسبه شده (Computed Members) باشند که مقدار آن‌ها در زمان اجرا تعیین می‌شود. این اعضا می‌توانند بر اساس توابع، متغیرها یا دیگر اعضای Enum محاسبه شوند.

    
            enum DynamicEnum {
                A = 1,
                B = Math.random() > 0.5 ? 2 : 3, // مقدار محاسبه شده در زمان اجرا
                C = A + B // این نیز یک مقدار محاسبه شده است.
            }
            console.log(DynamicEnum.B); // هر بار اجرا مقدار متفاوتی ممکن است داشته باشد
            
  • Const Enum:

    به هیچ وجه نمی‌توانند شامل اعضای محاسبه شده باشند. تمام مقادیر آن‌ها باید ثابت و در زمان کامپایل قابل تعیین باشند (Literal Values). این یک محدودیت جدی است که تضمین می‌کند inlining به طور کامل قابل انجام است.

    
            const enum StaticEnum {
                X = 1,
                // Y = new Date().getFullYear() // خطا: A 'const' enum member can only have a literal value.
                Z = "CONSTANT"
            }
            

بهترین روش‌ها و الگوهای جایگزین برای Enums در تایپ اسکریپت

انتخاب بین Enum و Const Enum به نیازهای خاص پروژه شما بستگی دارد. با این حال، چند ملاحظه و بهترین روش وجود دارد که می‌تواند به شما در تصمیم‌گیری کمک کند و کدی بهینه‌تر و قابل نگهداری‌تر بنویسید.

۱. ترجیح استفاده از Const Enums برای بهینه‌سازی و Tree Shaking

در بسیاری از موارد، اگر نیازی به دسترسی به Enum در زمان اجرا ندارید، همیشه استفاده از Const Enums را ترجیح دهید. این رویکرد پیش‌فرض برای اکثر پروژه‌های مدرن TypeScript است، به خصوص در محیط‌های Production که اندازه باندل و عملکرد بارگذاری اهمیت دارند. دلایل اصلی این ترجیح عبارتند از:

  • اندازه باندل کوچک‌تر: منجر به حداقل کد جاوا اسکریپت ممکن می‌شود.
  • عملکرد بهتر: عدم سربار در زمان اجرا، دسترسی مستقیم به مقادیر ثابت.
  • Tree Shaking کارآمدتر: کدهای غیرقابل استفاده به طور کامل حذف می‌شوند و فقط مقادیر لیترال مورد نیاز در باندل نهایی قرار می‌گیرند.

این رویکرد به خصوص برای ثابت‌های داخلی برنامه، پرچم‌های وضعیت، کدهای خطایی که فقط در مقایسه‌ها یا دستورات switch استفاده می‌شوند، و هر جایی که نیاز به یک مجموعه ثابت از مقادیر مشخص با Type Safety دارید، بسیار مناسب است.

۲. جایگزین‌های پیشرفته برای Enums (Object Literals با as const و Union Types)

گاهی اوقات، Enums بهترین راه حل نیستند، حتی اگر در ابتدا به نظر برسند. TypeScript راه‌های دیگری برای تعریف ثابت‌ها و مجموعه‌های محدود از مقادیر ارائه می‌دهد که ممکن است در برخی موارد مناسب‌تر باشند، به ویژه برای String Enums که نیاز به دسترسی در زمان اجرا دارند و همچنین مزایای بهینه‌سازی Const Enum را ارائه می‌دهند:

الف. استفاده از Object Literals با `as const`

TypeScript 3.4 قابلیت as const را معرفی کرد که به شما اجازه می‌دهد یک شیء را به عنوان یک “Literal Type” در نظر بگیرید. این به معنی است که TypeScript هر پراپرتی از شیء را به عنوان یک نوع لیترال (Literal Type) آن مقدار خاص در نظر می‌گیرد، نه فقط یک نوع عمومی (مانند string یا number).


const OrderState = {
    PENDING: "pending",
    PROCESSING: "processing",
    COMPLETED: "completed",
    CANCELLED: "cancelled"
} as const; // این 'as const' کلید ماجراست.

// تعریف یک نوع (Type) بر اساس مقادیر شیء بالا
type OrderStatusType = typeof OrderState[keyof typeof OrderState];
// اکنون OrderStatusType برابر با "pending" | "processing" | "completed" | "cancelled" است.

function updateOrder(orderId: string, state: OrderStatusType) {
    console.log(`سفارش با شناسه ${orderId} به وضعیت ${state} به‌روزرسانی شد.`);
}

updateOrder("ORD-001", OrderState.COMPLETED); // استفاده با نام‌گذاری معنایی
updateOrder("ORD-002", "pending"); // استفاده مستقیم از Literal Type نیز مجاز است
// updateOrder("ORD-003", "invalid"); // خطا در زمان کامپایل: Type '"invalid"' is not assignable to type 'OrderStatusType'.

console.log(OrderState.PENDING); // خروجی: pending
console.log(Object.keys(OrderState));   // خروجی: [ 'PENDING', 'PROCESSING', 'COMPLETED', 'CANCELLED' ] (دسترسی به کلیدها در زمان اجرا)
console.log(Object.values(OrderState)); // خروجی: [ 'pending', 'processing', 'completed', 'cancelled' ] (دسترسی به مقادیر در زمان اجرا)

مزایای این رویکرد:

  • کاملاً Tree Shakable: اگر هیچ عضوی از OrderState استفاده نشود، هیچ کدی برای آن تولید نمی‌شود. فقط مقادیر استفاده شده در باندل نهایی قرار می‌گیرند.
  • اندازه باندل بهینه: هیچ کد اضافی برای یک Enum تولید نمی‌شود، فقط یک شیء ساده جاوا اسکریپت که بهینه‌تر از Enum معمولی است.
  • دسترسی در زمان اجرا: می‌توانید به راحتی به کلیدها و مقادیر در زمان اجرا دسترسی داشته باشید (مانند Object.values(OrderState) یا Object.keys(OrderState)). این برای سناریوهایی که نیاز به تکرار بر روی مقادیر Enum یا نمایش آن‌ها در UI دارید، بسیار مفید است.
  • پشتیبانی از هر نوع مقداری: می‌توانید از اعداد، رشته‌ها، booleanها و حتی اشیاء به عنوان مقادیر استفاده کنید.
  • بدون Reverse Mapping ناخواسته: نیازی به نگرانی در مورد Reverse Mapping اضافی Numeric Enums نیست.

معایب:

  • ممکن است برای تیم‌هایی که به مفهوم Enum در زبان‌های دیگر عادت دارند، کمتر آشنا باشد.
  • نیاز به تعریف نوع جداگانه با typeof و keyof برای Type Safety در توابع، که ممکن است کمی پیچیده‌تر به نظر برسد.

این الگو به خصوص برای String Enums یک جایگزین عالی است، زیرا بسیاری از مزایای String Enums را حفظ کرده و در عین حال بهینه‌سازی‌های Const Enum را ارائه می‌دهد، به علاوه انعطاف‌پذیری دسترسی در زمان اجرا.

ب. استفاده از Union Types و Literal Types

برای مجموعه‌های کوچکی از ثابت‌ها، به خصوص اگر مقادیر آن‌ها فقط رشته‌ها یا اعداد ساده باشند، می‌توانید مستقیماً از Union Types استفاده کنید. این رویکرد بیشترین سادگی و بهینه‌سازی را فراهم می‌کند.


type UserRole = "admin" | "editor" | "viewer";

function assignRole(userId: string, role: UserRole) {
    console.log(`کاربر ${userId} نقش: ${role} را دریافت کرد.`);
}

assignRole("user1", "admin");
// assignRole("user2", "guest"); // خطا در زمان کامپایل: Type '"guest"' is not assignable to type 'UserRole'.

مزایای این رویکرد:

  • حداکثر بهینه‌سازی: هیچ کد جاوا اسکریپت اضافی تولید نمی‌شود. Type Safety فقط در زمان کامپایل اعمال می‌شود.
  • بسیار ساده و سبک: سادگی در تعریف و استفاده.

معایب:

  • برای مجموعه بزرگ‌تر از مقادیر، ممکن است خوانایی کد را کاهش دهد زیرا لیست Union Type طولانی می‌شود.
  • دسترسی به مقادیر ثابت با نام‌های معنایی (مانند UserRole.Admin) وجود ندارد. باید مستقیماً از رشته "admin" استفاده کنید.
  • مدیریت تغییرات و refactoring سخت‌تر است زیرا مقادیر به صورت رشته‌های خام در کد پخش شده‌اند.

۳. نام‌گذاری (Naming Conventions)

برای Enums (هم معمولی و هم const)، بهترین روش این است که از PascalCase برای نام Enum و از PascalCase یا SCREAMING_SNAKE_CASE برای اعضای آن استفاده کنید. این کار به وضوح نشان می‌دهد که یک متغیر یا مقدار، عضوی از یک Enum است و قابلیت خوانایی کد را افزایش می‌دهد.


enum UserPermissions {
    CanView = 1,
    CanEdit = 2,
    CanDelete = 4 // معمولاً برای Flag Enums استفاده می شود
}

const enum FileStatus {
    OPENED,
    CLOSED,
    SAVED_TO_DISK
}

مدیریت و حل مشکلات رایج در استفاده از Enums

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

۱. سوءتفاهم در مورد Const Enum و عدم وجود آن در زمان اجرا

شایع‌ترین اشتباه، تلاش برای دسترسی به یک Const Enum در زمان اجرا به شیوه‌ای است که با یک Enum معمولی انجام می‌شود. به یاد داشته باشید: Const Enum فقط در زمان کامپایل برای Type Checking و Inlining وجود دارد و در خروجی جاوا اسکریپت نهایی حذف می‌شود. هرگونه تلاش برای استفاده از Object.keys()، Object.values() یا دسترسی به مقادیر با استفاده از نگاشت معکوس (مانند MyConstEnum[0]) منجر به خطای کامپایل (در زمان Type Checking) یا رفتار غیرمنتظره (اگر Type Checker را دور بزنید و کد JS کامپایل شده را دستکاری کنید) خواهد شد.


const enum ColorCode {
    Red = 1,
    Green = 2
}

// این خط در زمان کامپایل خطا می‌دهد زیرا ColorCode یک تایپ است، نه یک مقدار در زمان اجرا
// console.log(Object.values(ColorCode)); // خطا: 'ColorCode' only refers to a type, but is being used as a value here.

// این خط نیز خطا می‌دهد
// console.log(ColorCode[1]); // خطا: A 'const' enum member cannot be accessed using a string literal.

۲. تفاوت `const` برای متغیرها و `const enum`

کلمه کلیدی const در TypeScript و JavaScript برای تعریف ثابت‌ها استفاده می‌شود. یک متغیر const فقط به این معنی است که نمی‌توانید آن متغیر را دوباره تخصیص دهید (reassign) و مقدار آن در زمان اجرا ثابت می‌ماند. اما const در کنار enum معنای متفاوتی دارد و به TypeScript دستور می‌دهد که این Enum را در زمان کامپایل “درج” (inline) کند و در زمان اجرا وجود نداشته باشد. این دو مفهوم، اگرچه از یک کلمه کلیدی استفاده می‌کنند، اما در سطوح و مکانیزم‌های مختلفی عمل می‌کنند.


const PI_VALUE = 3.14159; // یک ثابت عددی در زمان اجرا

const enum ThemeMode {
    LIGHT,
    DARK
}

// console.log(PI_VALUE); // خروجی: 3.14159
// console.log(ThemeMode.LIGHT); // خروجی JS: console.log(0);

// هر دو const هستند اما در سطوح مختلف عمل می‌کنند: PI_VALUE یک متغیر در runtime است، اما ThemeMode در runtime وجود ندارد.

۳. استفاده بی‌رویه از Enums برای ثابت‌های ساده

اگر فقط یک مقدار ثابت واحد دارید یا چند مقدار کاملاً نامرتبط، شاید تعریف یک Enum بیش از حد باشد و تنها به پیچیدگی کد اضافه کند. برای ثابت‌های تک، از const معمولی استفاده کنید. برای چند رشته یا عدد نامرتبط، شاید یک نوع union ساده کافی باشد.


// بهتر است از const معمولی استفاده شود
const MAX_UPLOAD_SIZE_MB = 10;
const DEFAULT_TIMEOUT_SECONDS = 30;

// برای مجموعه کوچک و ثابت از رشته ها، Union Type کافی است
type UserStatus = "active" | "inactive" | "suspended";

// اگرچه می توانید این کار را با enum انجام دهید، اما ممکن است اضافه کاری باشد و بهینه‌ترین نباشد
// enum MaxUpload { VALUE = 10 }
// enum StatusEnum { ACTIVE = "active", INACTIVE = "inactive", SUSPENDED = "suspended" }

تصمیم به استفاده از Enum باید بر اساس نیاز به گروه‌بندی مقادیر مرتبط، Type Safety و خوانایی کد باشد، نه صرفاً تعریف یک ثابت.

۴. انتخاب بین Numeric و String Enums

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

  • خوانایی بیشتر: مقادیر معنادارتر در لاگ‌ها، هنگام دیباگ، و در ارتباط با APIها.
  • سربار کمتر: عدم وجود Reverse Mapping (که یک ویژگی مفید Numeric Enums است اما هزینه دارد).
  • امنیت ضمنی بهتر: مقادیر String Enums که در HTML یا URLها استفاده می‌شوند، معمولاً برای انسان قابل فهم هستند. استفاده از اعداد خام ممکن است نیاز به نگاشت دستی داشته باشد و پتانسیل خطا را افزایش می‌دهد.

Numeric Enums عمدتاً زمانی مفید هستند که با یک API قدیمی کار می‌کنید که انتظار مقادیر عددی برای ثابت‌ها را دارد، یا زمانی که نیاز به استفاده از Reverse Mapping دارید (برای مثال تبدیل اعداد دریافتی از دیتابیس یا سرویس‌های خارجی به نام‌های خوانا در برنامه).

انتخاب درست: چه زمانی از Enum و چه زمانی از Const Enum استفاده کنیم؟

تصمیم‌گیری در مورد اینکه از Enum معمولی استفاده کنید یا Const Enum، به شدت به نیازهای خاص پروژه و ترجیحات تیم شما بستگی دارد. در اینجا یک راهنمای تصمیم‌گیری خلاصه ارائه می‌شود:

از Enum معمولی (بدون `const`) استفاده کنید اگر:

  • نیاز به دسترسی به Enum در زمان اجرا دارید:
    • قصد دارید بر روی اعضای Enum حلقه بزنید (مثلاً برای ساخت یک لیست کشویی در UI).
    • نیاز به استفاده از Object.keys()، Object.values() یا Object.entries() برای Enum دارید.
    • قصد دارید از Reverse Mapping (فقط برای Numeric Enums) برای تبدیل مقادیر عددی به نام‌های رشته‌ای استفاده کنید.
    • Enum شما شامل اعضای محاسبه شده (Computed Members) است که مقدار آن‌ها در زمان اجرا تعیین می‌شود.
  • با APIهای خارجی کار می‌کنید که انتظار اشیاء Enum کامل را دارند یا به ساختار Enum ران‌تایم وابسته هستند.
  • نگرانی اصلی شما خوانایی و انعطاف‌پذیری است، و افزایش جزئی اندازه باندل قابل قبول است.

مثال کاربرد: یک سیستم گزارش‌گیری که نیاز دارد تمام سطوح لاگ (INFO, WARN, ERROR) را به صورت دینامیک لیست کند و نمایش دهد.


enum LogLevel { Info, Warn, Error }

// ایجاد یک آرایه از نام های LogLevel برای نمایش در UI
const logLevels = Object.keys(LogLevel).filter(key => isNaN(Number(key)));
console.log(logLevels); // خروجی: [ 'Info', 'Warn', 'Error' ]

از Const Enum (با کلمه کلیدی `const`) استفاده کنید اگر:

  • هدف اصلی شما بهینه‌سازی، کاهش اندازه باندل و بهبود عملکرد است.
  • فقط به Type Safety در زمان کامپایل نیاز دارید.
  • مقادیر Enum شما ثابت (Literal) هستند و در زمان کامپایل قابل تعیین هستند.
  • نیازی به دسترسی به Enum در زمان اجرا ندارید و فقط مقادیر آن را در مقایسه‌ها یا تخصیص‌ها استفاده می‌کنید.
  • می‌خواهید Tree Shaking حداکثری را از باندلر خود بگیرید.

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


const enum HttpStatus {
    OK = 200,
    NotFound = 404,
    InternalServerError = 500
}

function handleResponse(code: HttpStatus) {
    if (code === HttpStatus.OK) {
        console.log("پاسخ موفقیت‌آمیز");
    } else if (code === HttpStatus.NotFound) {
        console.warn("منبع یافت نشد");
    } else {
        console.error("خطای ناشناخته سرور");
    }
}
// این کد در زمان کامپایل به handleResponse(200); handleResponse(404); و غیره تبدیل می شود.
handleResponse(HttpStatus.OK);

از Object Literals با `as const` یا Union Types استفاده کنید اگر:

  • برای String Enums، می‌خواهید بهینه‌سازی Const Enum را داشته باشید اما همچنان در زمان اجرا به نام‌ها و مقادیر دسترسی پیدا کنید. (as const)
  • برای مجموعه‌های بسیار کوچک و ساده از مقادیر که نیاز به نام‌گذاری رسمی Enum ندارند. (Union Types)

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

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

قیمت اصلی 2.290.000 ریال بود.قیمت فعلی 1.590.000 ریال است.

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

"با شرکت در این دوره جامع و کاربردی، به راحتی مهارت‌های برنامه‌نویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر می‌سازد تا به سرعت الگوریتم‌های پیچیده را درک کرده و اپلیکیشن‌های هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفه‌ای و امکان دانلود و تماشای آنلاین."

ویژگی‌های کلیدی:

بدون نیاز به تجربه قبلی برنامه‌نویسی

زیرنویس فارسی با ترجمه حرفه‌ای

۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان