جایگزینهای قدرتمند TypeScript برای enums را بررسی کنید: ادعاهای ثابت و انواع union. بیاموزید که چه زمانی از هر کدام برای کد قوی و قابل نگهداری استفاده کنید.
فراتر از Enums: ادعاهای ثابت TypeScript در مقابل انواع Union
در دنیای JavaScript با نوع داده ایستا با TypeScript، enums مدت هاست که یک گزینه اصلی برای نشان دادن مجموعه ای ثابت از ثابت های نامگذاری شده بوده اند. آنها یک راه واضح و خوانا برای تعریف مجموعه ای از مقادیر مرتبط ارائه می دهند. با این حال، با رشد و توسعه پروژه ها، توسعه دهندگان اغلب به دنبال جایگزین های انعطاف پذیرتر و گاهی اوقات با عملکرد بهتر هستند. دو رقیب قدرتمند که اغلب ظاهر می شوند ادعاهای ثابت و انواع union هستند. این پست به بررسی دقیق استفاده از این جایگزین ها برای enums سنتی می پردازد، مثال های عملی ارائه می دهد و شما را در انتخاب اینکه چه زمانی کدام یک را انتخاب کنید راهنمایی می کند.
درک Enums سنتی TypeScript
قبل از اینکه جایگزین ها را بررسی کنیم، ضروری است که درک درستی از نحوه عملکرد enums استاندارد TypeScript داشته باشیم. Enums به شما این امکان را می دهند که مجموعه ای از ثابت های عددی یا رشته ای نامگذاری شده را تعریف کنید. آنها می توانند عددی (پیش فرض) یا مبتنی بر رشته باشند.
Enums عددی
به طور پیش فرض، اعضای enum مقادیر عددی را از 0 اختصاص می دهند.
enum DirectionNumeric {
Up,
Down,
Left,
Right
}
let myDirection: DirectionNumeric = DirectionNumeric.Up;
console.log(myDirection); // Output: 0
همچنین می توانید به صراحت مقادیر عددی را اختصاص دهید.
enum StatusCode {
Success = 200,
NotFound = 404,
InternalError = 500
}
let responseStatus: StatusCode = StatusCode.Success;
console.log(responseStatus); // Output: 200
Enums رشته ای
Enums رشته ای اغلب به دلیل بهبود تجربه اشکال زدایی ترجیح داده می شوند، زیرا نام اعضا در JavaScript کامپایل شده حفظ می شوند.
enum ColorString {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
let favoriteColor: ColorString = ColorString.Blue;
console.log(favoriteColor); // Output: "BLUE"
هزینه سربار Enums
در حالی که enums راحت هستند، با کمی هزینه سربار همراه هستند. هنگامی که enums TypeScript به JavaScript کامپایل می شوند، به اشیایی تبدیل می شوند که اغلب دارای نگاشت های معکوس هستند (به عنوان مثال، نگاشت مقدار عددی به نام enum). این می تواند مفید باشد اما به اندازه بسته نیز کمک می کند و ممکن است همیشه ضروری نباشد.
این enum رشته ای ساده را در نظر بگیرید:
enum Status {
Pending = "PENDING",
Processing = "PROCESSING",
Completed = "COMPLETED"
}
در JavaScript، این ممکن است چیزی شبیه به این شود:
var Status;
(function (Status) {
Status["Pending"] = "PENDING";
Status["Processing"] = "PROCESSING";
Status["Completed"] = "COMPLETED";
})(Status || (Status = {}));
برای مجموعه های ساده و فقط خواندنی از ثابت ها، این کد تولید شده می تواند کمی بیش از حد باشد.
جایگزین 1: ادعاهای ثابت
ادعاهای ثابت یک ویژگی قدرتمند TypeScript است که به شما امکان می دهد به کامپایلر بگویید که خاص ترین نوع ممکن را برای یک مقدار استنباط کند. هنگامی که با آرایه ها یا اشیایی استفاده می شود که برای نشان دادن مجموعه ای ثابت از مقادیر در نظر گرفته شده اند، می توانند به عنوان یک جایگزین سبک وزن برای enums عمل کنند.
ادعاهای ثابت با آرایه ها
می توانید یک آرایه از لیترال های رشته ای ایجاد کنید و سپس از یک ادعای const استفاده کنید تا نوع آن را تغییرناپذیر و عناصر آن را انواع لیترال کنید.
const statusArray = ["PENDING", "PROCESSING", "COMPLETED"] as const;
type StatusType = typeof statusArray[number];
let currentStatus: StatusType = "PROCESSING";
// currentStatus = "FAILED"; // Error: Type '"FAILED"' is not assignable to type 'StatusType'.
function processStatus(status: StatusType) {
console.log(`Processing status: ${status}`);
}
processStatus("COMPLETED");
بیایید بررسی کنیم که در اینجا چه اتفاقی می افتد:
as const: این ادعا به TypeScript می گوید که آرایه را به عنوان فقط خواندنی در نظر بگیرد و خاص ترین انواع لیترال را برای عناصر آن استنباط کند. بنابراین، به جای `string[]`، نوع `readonly ["PENDING", "PROCESSING", "COMPLETED"]` می شود.typeof statusArray[number]: این یک نوع نگاشت شده است. روی تمام شاخص هایstatusArrayتکرار می شود و انواع لیترال آنها را استخراج می کند. امضای شاخصnumberاساساً می گوید "نوع هر عنصر در این آرایه را به من بده". نتیجه یک نوع union است:"PENDING" | "PROCESSING" | "COMPLETED".
این رویکرد ایمنی نوع مشابهی را برای enums رشته ای ارائه می دهد اما حداقل JavaScript تولید می کند. خود statusArray در JavaScript یک آرایه از رشته ها باقی می ماند.
ادعاهای ثابت با اشیاء
ادعاهای ثابت هنگام استفاده از اشیاء قدرتمندتر هستند. می توانید یک شی را تعریف کنید که در آن کلیدها نشان دهنده ثابت های نامگذاری شده شما هستند و مقادیر رشته ها یا اعداد لیترال هستند.
const userRoles = {
Admin: "ADMIN",
Editor: "EDITOR",
Viewer: "VIEWER"
} as const;
type UserRole = typeof userRoles[keyof typeof userRoles];
let currentUserRole: UserRole = "EDITOR";
// currentUserRole = "GUEST"; // Error: Type '"GUEST"' is not assignable to type 'UserRole'.
function displayRole(role: UserRole) {
console.log(`User role is: ${role}`);
}
displayRole(userRoles.Admin); // Valid
displayRole("EDITOR"); // Valid
در این مثال شی:
as const: این ادعا کل شی را فقط خواندنی می کند. مهمتر از آن، انواع لیترال را برای تمام مقادیر ویژگی (به عنوان مثال،"ADMIN"به جایstring) استنباط می کند و خود ویژگی ها را فقط خواندنی می کند.keyof typeof userRoles: این عبارت منجر به union از کلیدهای شیuserRolesمی شود، که"Admin" | "Editor" | "Viewer"است.typeof userRoles[keyof typeof userRoles]: این یک نوع جستجو است. union از کلیدها را می گیرد و از آن برای جستجوی مقادیر مربوطه در نوعuserRolesاستفاده می کند. این منجر به union از مقادیر می شود:"ADMIN" | "EDITOR" | "VIEWER"، که نوع مورد نظر ما برای نقش ها است.
خروجی JavaScript برای userRoles یک شی JavaScript ساده خواهد بود:
var userRoles = {
Admin: "ADMIN",
Editor: "EDITOR",
Viewer: "VIEWER"
};
این به طور قابل توجهی سبک تر از یک enum معمولی است.
چه زمانی از ادعاهای ثابت استفاده کنیم
- ثابت های فقط خواندنی: زمانی که به مجموعه ای ثابت از لیترال های رشته ای یا عددی نیاز دارید که نباید در زمان اجرا تغییر کنند.
- حداقل خروجی JavaScript: اگر نگران اندازه بسته هستید و می خواهید بهترین نمایش زمان اجرا را برای ثابت های خود داشته باشید.
- ساختار شبیه به شی: زمانی که خوانایی جفت های کلید-مقدار را ترجیح می دهید، شبیه به نحوه ساختار داده ها یا پیکربندی.
- مجموعه های مبتنی بر رشته: به ویژه برای نشان دادن حالات، انواع یا دسته بندی هایی که به بهترین وجه توسط رشته های توصیفی شناسایی می شوند، مفید است.
جایگزین 2: انواع Union
انواع Union به شما این امکان را می دهند که اعلام کنید یک متغیر می تواند مقداری از یکی از چندین نوع را در خود نگه دارد. هنگامی که با انواع لیترال (لیترال های رشته ای، عددی، بولی) ترکیب می شوند، یک راه قدرتمند برای تعریف مجموعه ای از مقادیر مجاز بدون نیاز به اعلان ثابت صریح برای خود مجموعه تشکیل می دهند.
انواع Union با لیترال های رشته ای
می توانید مستقیماً union از لیترال های رشته ای را تعریف کنید.
type TrafficLightColor = "RED" | "YELLOW" | "GREEN";
let currentLight: TrafficLightColor = "YELLOW";
// currentLight = "BLUE"; // Error: Type '"BLUE"' is not assignable to type 'TrafficLightColor'.
function changeLight(color: TrafficLightColor) {
console.log(`Changing light to: ${color}`);
}
changeLight("RED");
// changeLight("REDDY"); // Error
این مستقیم ترین و اغلب مختصرترین راه برای تعریف مجموعه ای از مقادیر رشته ای مجاز است.
انواع Union با لیترال های عددی
به طور مشابه، می توانید از لیترال های عددی استفاده کنید.
type HttpStatusCode = 200 | 400 | 404 | 500;
let responseCode: HttpStatusCode = 404;
// responseCode = 201; // Error: Type '201' is not assignable to type 'HttpStatusCode'.
function handleResponse(code: HttpStatusCode) {
if (code === 200) {
console.log("Success!");
} else {
console.log(`Error code: ${code}`);
}
}
handleResponse(500);
چه زمانی از انواع Union استفاده کنیم
- مجموعه های ساده و مستقیم: زمانی که مجموعه مقادیر مجاز کوچک، واضح است و فراتر از خود مقادیر نیازی به کلیدهای توصیفی ندارد.
- ثابت های ضمنی: زمانی که نیازی به ارجاع به یک ثابت نامگذاری شده برای خود مجموعه ندارید، بلکه مستقیماً از مقادیر لیترال استفاده می کنید.
- حداکثر اختصار: برای سناریوهای ساده که تعریف یک شی یا آرایه اختصاصی احساس می شود بیش از حد است.
- پارامترهای/انواع برگشتی تابع: عالی برای تعریف مجموعه دقیق ورودی/خروجی رشته ای یا عددی قابل قبول برای توابع.
مقایسه Enums، ادعاهای ثابت و انواع Union
بیایید تفاوت های کلیدی و موارد استفاده را خلاصه کنیم:
رفتار زمان اجرا
- Enums: اشیاء JavaScript را تولید می کنند، به طور بالقوه با نگاشت های معکوس.
- ادعاهای ثابت (آرایه ها/اشیاء): آرایه ها یا اشیاء JavaScript ساده را تولید می کنند. اطلاعات نوع در زمان اجرا پاک می شوند، اما ساختار داده باقی می ماند.
- انواع Union (با لیترال ها): هیچ نمایش زمان اجرا برای خود union وجود ندارد. مقادیر فقط لیترال هستند. بررسی نوع صرفاً در زمان کامپایل اتفاق می افتد.
خوانایی و رسا بودن
- Enums: خوانایی بالا، به خصوص با نام های توصیفی. می تواند پرمخاطب تر باشد.
- ادعاهای ثابت (اشیاء): خوانایی خوب از طریق جفت های کلید-مقدار، تقلید از پیکربندی ها یا تنظیمات.
- ادعاهای ثابت (آرایه ها): برای نشان دادن ثابت های نامگذاری شده، بیشتر برای یک لیست مرتب از مقادیر، خوانایی کمتری دارد.
- انواع Union: بسیار مختصر. خوانایی به وضوح خود مقادیر لیترال بستگی دارد.
ایمنی نوع
- هر سه رویکرد ایمنی نوع قوی را ارائه می دهند. آنها اطمینان می دهند که فقط مقادیر معتبر و از پیش تعریف شده می توانند به متغیرها اختصاص داده شوند یا به توابع منتقل شوند.
اندازه بسته
- Enums: به طور کلی بزرگترین به دلیل اشیاء JavaScript تولید شده.
- ادعاهای ثابت: کوچکتر از enums، زیرا ساختارهای داده ساده را تولید می کنند.
- انواع Union: کوچکترین، زیرا هیچ ساختار داده زمان اجرا خاصی را برای خود نوع تولید نمی کنند، فقط به مقادیر لیترال تکیه می کنند.
ماتریس موارد استفاده
در اینجا یک راهنمای سریع آورده شده است:
| ویژگی | TypeScript Enum | ادعای ثابت (شی) | ادعای ثابت (آرایه) | نوع Union (لیترال ها) |
|---|---|---|---|---|
| خروجی زمان اجرا | شی JS (با نگاشت معکوس) | شی JS ساده | آرایه JS ساده | هیچ (فقط مقادیر لیترال) |
| خوانایی (ثابت های نامگذاری شده) | بالا | بالا | متوسط | پایین (مقادیر نام هستند) |
| اندازه بسته | بزرگترین | متوسط | متوسط | کوچکترین |
| انعطاف پذیری | خوب | خوب | خوب | عالی (برای مجموعه های ساده) |
| استفاده رایج | حالات، کدهای وضعیت، دسته بندی ها | پیکربندی، تعاریف نقش، پرچم های ویژگی | لیست های مرتب از مقادیر تغییرناپذیر | پارامترهای تابع، مقادیر محدود ساده |
مثال های عملی و بهترین شیوه ها
مثال 1: نشان دادن کدهای وضعیت API
Enum:
enum ApiStatus {
Success = "SUCCESS",
Error = "ERROR",
Pending = "PENDING"
}
function handleApiResponse(status: ApiStatus) {
// ... logic ...
}
ادعای ثابت (شی):
const apiStatusCodes = {
SUCCESS: "SUCCESS",
ERROR: "ERROR",
PENDING: "PENDING"
} as const;
type ApiStatus = typeof apiStatusCodes[keyof typeof apiStatusCodes];
function handleApiResponse(status: ApiStatus) {
// ... logic ...
}
نوع Union:
type ApiStatus = "SUCCESS" | "ERROR" | "PENDING";
function handleApiResponse(status: ApiStatus) {
// ... logic ...
}
توصیه: برای این سناریو، یک نوع union اغلب مختصرترین و کارآمدترین است. خود مقادیر لیترال به اندازه کافی توصیفی هستند. اگر نیاز به مرتبط کردن ابرداده اضافی با هر وضعیت (به عنوان مثال، یک پیام کاربرپسند) دارید، یک شی ادعای ثابت انتخاب بهتری خواهد بود.
مثال 2: تعریف نقش های کاربر
Enum:
enum UserRoleEnum {
Admin = "ADMIN",
Moderator = "MODERATOR",
User = "USER"
}
function getUserPermissions(role: UserRoleEnum) {
// ... logic ...
}
ادعای ثابت (شی):
const userRolesObject = {
Admin: "ADMIN",
Moderator: "MODERATOR",
User: "USER"
} as const;
type UserRole = typeof userRolesObject[keyof typeof userRolesObject];
function getUserPermissions(role: UserRole) {
// ... logic ...
}
نوع Union:
type UserRole = "ADMIN" | "MODERATOR" | "USER";
function getUserPermissions(role: UserRole) {
// ... logic ...
}
توصیه: یک شی ادعای ثابت در اینجا تعادل خوبی ایجاد می کند. این جفت های کلید-مقدار واضحی را ارائه می دهد (به عنوان مثال، userRolesObject.Admin) که می تواند خوانایی را هنگام ارجاع به نقش ها بهبود بخشد، در حالی که همچنان عملکرد خوبی دارد. اگر لیترال های رشته ای مستقیم کافی باشند، یک نوع union نیز یک رقیب بسیار قوی است.
مثال 3: نشان دادن گزینه های پیکربندی
یک شی پیکربندی را برای یک برنامه جهانی تصور کنید که ممکن است تم های مختلفی داشته باشد.
Enum:
enum Theme {
Light = "light",
Dark = "dark",
System = "system"
}
interface AppConfig {
theme: Theme;
// ... other config options ...
}
ادعای ثابت (شی):
const themes = {
Light: "light",
Dark: "dark",
System: "system"
} as const;
type Theme = typeof themes[keyof typeof themes];
interface AppConfig {
theme: Theme;
// ... other config options ...
}
نوع Union:
type Theme = "light" | "dark" | "system";
interface AppConfig {
theme: Theme;
// ... other config options ...
}
توصیه: برای تنظیمات پیکربندی مانند تم ها، شی ادعای ثابت اغلب ایده آل است. این به وضوح گزینه های موجود و مقادیر رشته ای مربوطه آنها را تعریف می کند. کلیدها (Light، Dark، System) توصیفی هستند و مستقیماً به مقادیر نگاشت می شوند، و کد پیکربندی را بسیار قابل درک می کنند.
انتخاب ابزار مناسب برای کار
تصمیم بین enums TypeScript، ادعاهای ثابت و انواع union همیشه سیاه و سفید نیست. این اغلب به یک مصالحه بین عملکرد زمان اجرا، اندازه بسته و خوانایی/رسا بودن کد می رسد.
- زمانی که به یک مجموعه ساده و محدود از لیترال های رشته ای یا عددی نیاز دارید و حداکثر اختصار مورد نظر است، انواع Union را انتخاب کنید. آنها برای امضاهای تابع و محدودیت های مقدار اساسی عالی هستند.
- زمانی که می خواهید یک روش ساخت یافته تر و خواناتر برای تعریف ثابت های نامگذاری شده، شبیه به یک enum، اما با سربار زمان اجرای بسیار کمتر، داشته باشید، ادعاهای ثابت (با اشیاء) را انتخاب کنید. این برای پیکربندی، نقش ها یا هر مجموعه ای که در آن کلیدها معنای قابل توجهی اضافه می کنند، عالی است.
- زمانی که به سادگی به یک لیست مرتب تغییرناپذیر از مقادیر نیاز دارید و دسترسی مستقیم از طریق شاخص مهمتر از کلیدهای نامگذاری شده است، ادعاهای ثابت (با آرایه ها) را انتخاب کنید.
- زمانی که به ویژگی های خاص آنها نیاز دارید، مانند نگاشت معکوس (اگرچه این در توسعه مدرن کمتر رایج است) یا اگر تیم شما ترجیح زیادی دارد و تأثیر عملکرد برای پروژه شما ناچیز است، Enums TypeScript را در نظر بگیرید.
در بسیاری از پروژه های مدرن TypeScript، به دلیل ویژگی های عملکرد بهتر و خروجی JavaScript اغلب ساده تر، گرایشی به سمت ادعاهای ثابت و انواع union نسبت به enums سنتی، به ویژه برای ثابت های مبتنی بر رشته، خواهید یافت.
ملاحظات جهانی
هنگام توسعه برنامه ها برای مخاطبان جهانی، تعاریف ثابت سازگار و قابل پیش بینی بسیار مهم هستند. انتخاب هایی که مورد بحث قرار دادیم (enums، ادعاهای ثابت، انواع union) همگی با اعمال ایمنی نوع در محیط ها و محله های مختلف توسعه دهنده، به این سازگاری کمک می کنند.
- سازگاری: صرف نظر از روش انتخابی، نکته اصلی سازگاری در پروژه شما است. اگر تصمیم دارید از اشیاء ادعای ثابت برای نقش ها استفاده کنید، به این الگو در سراسر کدبیس پایبند باشید.
- بین المللی سازی (i18n): هنگام تعریف برچسب ها یا پیام هایی که بین المللی می شوند، از این ساختارهای ایمن نوع استفاده کنید تا اطمینان حاصل شود که فقط از کلیدها یا شناسه های معتبر استفاده می شود. رشته های ترجمه شده واقعی به طور جداگانه از طریق کتابخانه های i18n مدیریت می شوند. به عنوان مثال، اگر یک فیلد `status` دارید که می تواند "PENDING"، "PROCESSING"، "COMPLETED" باشد، کتابخانه i18n شما این شناسه های داخلی را به متن نمایشی محلی سازی شده نگاشت می کند.
- مناطق زمانی و ارزها: در حالی که مستقیماً به enums مرتبط نیست، به خاطر داشته باشید که هنگام برخورد با مقادیری مانند تاریخ، زمان یا ارزها، سیستم نوع TypeScript می تواند به اعمال استفاده صحیح کمک کند، اما کتابخانه های خارجی معمولاً برای رسیدگی دقیق جهانی ضروری هستند. به عنوان مثال، یک نوع union `Currency` می تواند به صورت `"USD" | "EUR" | "GBP"` تعریف شود، اما منطق تبدیل واقعی به ابزارهای تخصصی نیاز دارد.
نتیجه گیری
TypeScript مجموعه ای غنی از ابزارها را برای مدیریت ثابت ها ارائه می دهد. در حالی که enums به خوبی به ما خدمت کرده اند، ادعاهای ثابت و انواع union جایگزین های قانع کننده و اغلب با عملکرد بهتری را ارائه می دهند. با درک تفاوت های آنها و انتخاب رویکرد صحیح بر اساس نیازهای خاص خود - چه عملکرد، خوانایی یا اختصار باشد - می توانید کد TypeScript قوی تر، قابل نگهداری و کارآمدتری بنویسید که به طور جهانی مقیاس پذیر باشد.
پذیرش این جایگزین ها می تواند منجر به اندازه بسته کوچکتر، برنامه های سریعتر و تجربه توسعه دهنده قابل پیش بینی تر برای تیم بین المللی شما شود.