TypeScript - چگونه یک Enum را با مقدار آن جستجو کنیم؟


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

export enum API_MESSAGES {
  FAILED_TO_LOAD = 'Failed to load data',
  TOKEN_INVALID = 'Token seems to be invalid',
}

میخواهم از خطای دریافتی از بخش پشتی برای گرفتن مقدار ترجمهشده استفاده کنم:

onError: (error: AxiosError<ApiError>) => {
  const errorRes = error.response?.data.error;
  console.log(API_MESSAGES[errorRes])
}

با این حال، این خطای TS زیر را ایجاد میکند:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'typeof API_MESSAGES'

No index signature with a parameter of type 'string' was found on type 'typeof API_MESSAGES'

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

پاسخ شماره ۱

رشتههای شمارشی از نگاشت معکوس پشتیبانی نمیکنند

API_MESSAGES[errorRes] منجر به خطا میشود زیرا طبق کتاب راهنمای تایپاسکریپت:

نگاشتهای معکوس

علاوه بر ایجاد یک شیء با نامهای خصوصیت برای اعضا، اعضای شمارش عددی همچنین یک نگاشت معکوس از مقادیر شمارش به نامهای شمارش دریافت میکنند.

یک شمارۀ شمارشی است

کامپایل شده به یک شیء که هر دو نگاشت پیشرو (نام -> مقدار) و معکوس (مقدار -> نام) را ذخیره میکند.

به خاطر داشته باشید که اعضای شمارنده رشتهای اصلاً نگاشت معکوس تولید نمیکنند.

اما شما به هر حال نمیخواهید یک نگاشت معکوس داشته باشید!

هدف شما این است که پیغام API «ترجمه شده» (مناسب برای انسان) را نمایش دهید. بنابراین حتی اگر API_MESSAGES[errorRes] برای رشتههای Enum پشتیبانی میشد، شما این کار را انجام میدادید:

'Token seems to be invalid' --> TOKEN_INVALID 

یعنی برعکس چیزی که میخواهید.

اگر بخش پشتی شما از همان تعریف شمارشی استفاده میکند، در واقع مقدار «ترجمهشده» را قبلاً ارسال کرده است!

فرض کنید کد بکاند شما از همان enum API_MESSAGES استفاده میکند، در این صورت در واقع همه کاری که انجام میدهد این است که از «یک مجموعه ثابتهای نامدار» استفاده میکند، به عنوان مثال FAILED_TO_LOAD و TOKEN_INVALID، که مقادیر آنها خود پیامهای ترجمه شده هستند.

به عبارت دیگر، اگر بخش پشتی شما صرفاً API_MESSAGES را در پاسخ خود به بخش جلویی سریال میکند، در واقع پیام «ترجمهشده» را ارسال میکند.

که به این معنی است که تابع onError شما فقط این خواهد بود:

onError: (error: AxiosError<ApiError>) => {
  const errorRes = error.response?.data.error;
  console.log(errorRes)
}

از دیدگاه تایپاسکریپت و آنچه منطق شما بیان میکند، error.response?.data.error نوع API_MESSAGES را دارد. از آنجایی که انامهای تایپاسکریپت در واقع فقط ثابتهای نامگذاری شده هستند، این بدان معنی است که error.response?.data.error به طور مؤثر نوع اجتماع زیر خواهد بود

type API_MESSAGES = 'Failed to load data' | 'Token seems to be invalid',

پس دوباره، شما قبلاً مقدار ترجمهشده را خواهید داشت. FAILED_TO_LOAD و TOKEN_INVALID فقط نامهای تایپاسکریپت برای آن مقادیر خواهند بود. اگر بکاند شما از همان تعریف انوم استفاده میکند.

اگر بکاند شما کدها را ارسال میکند، شما تنها به یک شیء نگاشت نیاز دارید

اگر FAILED_TO_LOAD و TOKEN_INVALID واقعاً رشتههای پیامی هستند که توسط بکاند ارسال میشوند، شما فقط به یک شیء نگاشت به جای یک enum نیاز دارید:

export const API_MESSAGES = {
  FAILED_TO_LOAD = 'Failed to load data',
  TOKEN_INVALID = 'Token seems to be invalid',
} as const;

// type API_MESSAGE = 'FAILED_TO_LOAD' | 'TOKEN_INVALID'
export type API_MESSAGE = keyof typeof API_MESSAGES

کد خطایی که از قسمت پشتی ارسال میشود، فقط یک رشته ساده است و یک Enum تایپشده نیست. این تابع convertServerMessage نشان میدهد که چگونه آن را اعتبارسنجی کرده و به Enum API_MESSAGE تبدیل کنیم. همانطور که قبلاً گفتید، این کاری است که باید جایی انجام شود. احتمالاً به این روش جداگانه نیازی نخواهید داشت -- به جای آن میتوانید منطق آن را در بخشی از کد خود قرار دهید که پاسخ سرور را میخواند و شیء AxiosError را میسازد، که AxiosError.response.data.error باید از نوع API_MESSAGE باشد.

export function convertServerMessage(msg: string): API_MESSAGE {
    if (msg in API_MESSAGES) {
        return msg as API_MESSAGE
    } else {
        // error logic goes here
    }
}

سپس onError اصلی شما همانطور که میخواستید کار خواهد کرد (به یادداشت در پایین مراجعه کنید):

onError: (error: AxiosError<ApiError>) => {
    // as I said above, the conversion from string code to 
    // enum should probably happen elsewere. I have it here
    // to keep the example simple.
    const errorRes = convertServerMessage(error.response?.data.error);
    console.log(API_MESSAGES[errorRes])
}